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.List;
012import java.util.Map;
013import java.util.concurrent.TimeUnit;
014
015import com.box.sdk.internal.utils.Parsers;
016import com.eclipsesource.json.JsonArray;
017import com.eclipsesource.json.JsonObject;
018import com.eclipsesource.json.JsonValue;
019/**
020 * Represents a folder on Box. This class can be used to iterate through a folder's contents, collaborate a folder with
021 * another user or group, and perform other common folder operations (move, copy, delete, etc.).
022 * <p>
023 * <p>Unless otherwise noted, the methods in this class can throw an unchecked {@link BoxAPIException} (unchecked
024 * meaning that the compiler won't force you to handle it) if an error occurs. If you wish to implement custom error
025 * handling for errors related to the Box REST API, you should capture this exception explicitly.</p>
026 */
027@BoxResourceType("folder")
028public class BoxFolder extends BoxItem implements Iterable<BoxItem.Info> {
029    /**
030     * An array of all possible folder fields that can be requested when calling {@link #getInfo()}.
031     */
032    public static final String[] ALL_FIELDS = {"type", "id", "sequence_id", "etag", "name", "created_at", "modified_at",
033        "description", "size", "path_collection", "created_by", "modified_by", "trashed_at", "purged_at",
034        "content_created_at", "content_modified_at", "owned_by", "shared_link", "folder_upload_email", "parent",
035        "item_status", "item_collection", "sync_state", "has_collaborations", "permissions", "tags",
036        "can_non_owners_invite", "collections", "watermark_info", "metadata", "is_externally_owned",
037        "is_collaboration_restricted_to_enterprise", "allowed_shared_link_access_levels", "allowed_invitee_roles"};
038
039    /**
040     * Used to specify what direction to sort and display results.
041     */
042    public enum SortDirection {
043        /**
044         * ASC for ascending order.
045         */
046        ASC,
047
048        /**
049         * DESC for descending order.
050         */
051        DESC
052    }
053
054    /**
055     * Create Folder URL Template.
056     */
057    public static final URLTemplate CREATE_FOLDER_URL = new URLTemplate("folders");
058    /**
059     * Create Web Link URL Template.
060     */
061    public static final URLTemplate CREATE_WEB_LINK_URL = new URLTemplate("web_links");
062    /**
063     * Copy Folder URL Template.
064     */
065    public static final URLTemplate COPY_FOLDER_URL = new URLTemplate("folders/%s/copy");
066    /**
067     * Delete Folder URL Template.
068     */
069    public static final URLTemplate DELETE_FOLDER_URL = new URLTemplate("folders/%s?recursive=%b");
070    /**
071     * Folder Info URL Template.
072     */
073    public static final URLTemplate FOLDER_INFO_URL_TEMPLATE = new URLTemplate("folders/%s");
074    /**
075     * Upload File URL Template.
076     */
077    public static final URLTemplate UPLOAD_FILE_URL = new URLTemplate("files/content");
078    /**
079     * Add Collaboration URL Template.
080     */
081    public static final URLTemplate ADD_COLLABORATION_URL = new URLTemplate("collaborations");
082    /**
083     * Get Collaborations URL Template.
084     */
085    public static final URLTemplate GET_COLLABORATIONS_URL = new URLTemplate("folders/%s/collaborations");
086    /**
087     * Get Items URL Template.
088     */
089    public static final URLTemplate GET_ITEMS_URL = new URLTemplate("folders/%s/items/");
090    /**
091     * Search URL Template.
092     */
093    public static final URLTemplate SEARCH_URL_TEMPLATE = new URLTemplate("search");
094    /**
095     * Metadata URL Template.
096     */
097    public static final URLTemplate METADATA_URL_TEMPLATE = new URLTemplate("folders/%s/metadata/%s/%s");
098    /**
099     * Upload Session URL Template.
100     */
101    public static final URLTemplate UPLOAD_SESSION_URL_TEMPLATE = new URLTemplate("files/upload_sessions");
102    /**
103     * Folder Locks URL Template.
104     */
105    public static final URLTemplate FOLDER_LOCK_URL_TEMPLATE = new URLTemplate("folder_locks");
106
107    /**
108     * Constructs a BoxFolder for a folder with a given ID.
109     *
110     * @param api the API connection to be used by the folder.
111     * @param id  the ID of the folder.
112     */
113    public BoxFolder(BoxAPIConnection api, String id) {
114        super(api, id);
115    }
116
117    /**
118     * {@inheritDoc}
119     */
120    @Override
121    protected URL getItemURL() {
122        return FOLDER_INFO_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
123    }
124
125    /**
126     * Gets the current user's root folder.
127     *
128     * @param api the API connection to be used by the folder.
129     * @return the user's root folder.
130     */
131    public static BoxFolder getRootFolder(BoxAPIConnection api) {
132        return new BoxFolder(api, "0");
133    }
134
135    /**
136     * Adds a collaborator to this folder.
137     *
138     * @param collaborator the collaborator to add.
139     * @param role         the role of the collaborator.
140     * @return info about the new collaboration.
141     */
142    public BoxCollaboration.Info collaborate(BoxCollaborator collaborator, BoxCollaboration.Role role) {
143        JsonObject accessibleByField = new JsonObject();
144        accessibleByField.add("id", collaborator.getID());
145
146        if (collaborator instanceof BoxUser) {
147            accessibleByField.add("type", "user");
148        } else if (collaborator instanceof BoxGroup) {
149            accessibleByField.add("type", "group");
150        } else {
151            throw new IllegalArgumentException("The given collaborator is of an unknown type.");
152        }
153
154        return this.collaborate(accessibleByField, role, null, null);
155    }
156
157    /**
158     * Adds a collaborator to this folder. An email will be sent to the collaborator if they don't already have a Box
159     * account.
160     *
161     * @param email the email address of the collaborator to add.
162     * @param role  the role of the collaborator.
163     * @return info about the new collaboration.
164     */
165    public BoxCollaboration.Info collaborate(String email, BoxCollaboration.Role role) {
166        JsonObject accessibleByField = new JsonObject();
167        accessibleByField.add("login", email);
168        accessibleByField.add("type", "user");
169
170        return this.collaborate(accessibleByField, role, null, null);
171    }
172
173    /**
174     * Adds a collaborator to this folder.
175     *
176     * @param collaborator the collaborator to add.
177     * @param role         the role of the collaborator.
178     * @param notify       the user/group should receive email notification of the collaboration or not.
179     * @param canViewPath  the view path collaboration feature is enabled or not.
180     *                     View path collaborations allow the invitee to see the entire ancestral path to the associated
181     *                     folder. The user will not gain privileges in any ancestral folder.
182     * @return info about the new collaboration.
183     */
184    public BoxCollaboration.Info collaborate(BoxCollaborator collaborator, BoxCollaboration.Role role,
185                                             Boolean notify, Boolean canViewPath) {
186        JsonObject accessibleByField = new JsonObject();
187        accessibleByField.add("id", collaborator.getID());
188
189        if (collaborator instanceof BoxUser) {
190            accessibleByField.add("type", "user");
191        } else if (collaborator instanceof BoxGroup) {
192            accessibleByField.add("type", "group");
193        } else {
194            throw new IllegalArgumentException("The given collaborator is of an unknown type.");
195        }
196
197        return this.collaborate(accessibleByField, role, notify, canViewPath);
198    }
199
200    /**
201     * Adds a collaborator to this folder. An email will be sent to the collaborator if they don't already have a Box
202     * account.
203     *
204     * @param email       the email address of the collaborator to add.
205     * @param role        the role of the collaborator.
206     * @param notify      the user/group should receive email notification of the collaboration or not.
207     * @param canViewPath the view path collaboration feature is enabled or not.
208     *                    View path collaborations allow the invitee to see the entire ancestral path to the associated
209     *                    folder. The user will not gain privileges in any ancestral folder.
210     * @return info about the new collaboration.
211     */
212    public BoxCollaboration.Info collaborate(String email, BoxCollaboration.Role role,
213                                             Boolean notify, Boolean canViewPath) {
214        JsonObject accessibleByField = new JsonObject();
215        accessibleByField.add("login", email);
216        accessibleByField.add("type", "user");
217
218        return this.collaborate(accessibleByField, role, notify, canViewPath);
219    }
220
221    private BoxCollaboration.Info collaborate(JsonObject accessibleByField, BoxCollaboration.Role role,
222                                              Boolean notify, Boolean canViewPath) {
223
224        JsonObject itemField = new JsonObject();
225        itemField.add("id", this.getID());
226        itemField.add("type", "folder");
227
228        return BoxCollaboration.create(this.getAPI(), accessibleByField, itemField, role, notify, canViewPath);
229    }
230
231    @Override
232    public BoxSharedLink createSharedLink(BoxSharedLink.Access access, Date unshareDate,
233                                          BoxSharedLink.Permissions permissions) {
234
235        BoxSharedLink sharedLink = new BoxSharedLink(access, unshareDate, permissions);
236        Info info = new Info();
237        info.setSharedLink(sharedLink);
238
239        this.updateInfo(info);
240        return info.getSharedLink();
241    }
242
243    /**
244     * Creates new SharedLink for a BoxFolder with a password.
245     *
246     * @param access        The access level of the shared link.
247     * @param unshareDate   A specified date to unshare the Box folder.
248     * @param permissions   The permissions to set on the shared link for the Box folder.
249     * @param password      Password set on the shared link to give access to the Box folder.
250     * @return information about the newly created shared link.
251     */
252    public BoxSharedLink createSharedLink(BoxSharedLink.Access access, Date unshareDate,
253                                          BoxSharedLink.Permissions permissions, String password) {
254
255        BoxSharedLink sharedLink = new BoxSharedLink(access, unshareDate, permissions, password);
256        Info info = new Info();
257        info.setSharedLink(sharedLink);
258
259        this.updateInfo(info);
260        return info.getSharedLink();
261    }
262
263    /**
264     * Gets information about all of the collaborations for this folder.
265     *
266     * @return a collection of information about the collaborations for this folder.
267     */
268    public Collection<BoxCollaboration.Info> getCollaborations() {
269        BoxAPIConnection api = this.getAPI();
270        URL url = GET_COLLABORATIONS_URL.build(api.getBaseURL(), this.getID());
271
272        BoxAPIRequest request = new BoxAPIRequest(api, url, "GET");
273        BoxJSONResponse response = (BoxJSONResponse) request.send();
274        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
275
276        int entriesCount = responseJSON.get("total_count").asInt();
277        Collection<BoxCollaboration.Info> collaborations = new ArrayList<BoxCollaboration.Info>(entriesCount);
278        JsonArray entries = responseJSON.get("entries").asArray();
279        for (JsonValue entry : entries) {
280            JsonObject entryObject = entry.asObject();
281            BoxCollaboration collaboration = new BoxCollaboration(api, entryObject.get("id").asString());
282            BoxCollaboration.Info info = collaboration.new Info(entryObject);
283            collaborations.add(info);
284        }
285
286        return collaborations;
287    }
288
289    @Override
290    public BoxFolder.Info getInfo() {
291        URL url = FOLDER_INFO_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
292        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
293        BoxJSONResponse response = (BoxJSONResponse) request.send();
294        return new Info(response.getJSON());
295    }
296
297    @Override
298    public BoxFolder.Info getInfo(String... fields) {
299        String queryString = new QueryStringBuilder().appendParam("fields", fields).toString();
300        URL url = FOLDER_INFO_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
301
302        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
303        BoxJSONResponse response = (BoxJSONResponse) request.send();
304        return new Info(response.getJSON());
305    }
306
307    /**
308     * Updates the information about this folder with any info fields that have been modified locally.
309     *
310     * @param info the updated info.
311     */
312    public void updateInfo(BoxFolder.Info info) {
313        URL url = FOLDER_INFO_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
314        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
315        request.setBody(info.getPendingChanges());
316        BoxJSONResponse response = (BoxJSONResponse) request.send();
317        JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
318        info.update(jsonObject);
319    }
320
321    @Override
322    public BoxFolder.Info copy(BoxFolder destination) {
323        return this.copy(destination, null);
324    }
325
326    @Override
327    public BoxFolder.Info copy(BoxFolder destination, String newName) {
328        URL url = COPY_FOLDER_URL.build(this.getAPI().getBaseURL(), this.getID());
329        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
330
331        JsonObject parent = new JsonObject();
332        parent.add("id", destination.getID());
333
334        JsonObject copyInfo = new JsonObject();
335        copyInfo.add("parent", parent);
336        if (newName != null) {
337            copyInfo.add("name", newName);
338        }
339
340        request.setBody(copyInfo.toString());
341        BoxJSONResponse response = (BoxJSONResponse) request.send();
342        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
343        BoxFolder copiedFolder = new BoxFolder(this.getAPI(), responseJSON.get("id").asString());
344        return copiedFolder.new Info(responseJSON);
345    }
346
347    /**
348     * Creates a new child folder inside this folder.
349     *
350     * @param name the new folder's name.
351     * @return the created folder's info.
352     */
353    public BoxFolder.Info createFolder(String name) {
354        JsonObject parent = new JsonObject();
355        parent.add("id", this.getID());
356
357        JsonObject newFolder = new JsonObject();
358        newFolder.add("name", name);
359        newFolder.add("parent", parent);
360
361        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), CREATE_FOLDER_URL.build(this.getAPI().getBaseURL()),
362                "POST");
363        request.setBody(newFolder.toString());
364        BoxJSONResponse response = (BoxJSONResponse) request.send();
365        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
366
367        BoxFolder createdFolder = new BoxFolder(this.getAPI(), responseJSON.get("id").asString());
368        return createdFolder.new Info(responseJSON);
369    }
370
371    /**
372     * Deletes this folder, optionally recursively deleting all of its contents.
373     *
374     * @param recursive true to recursively delete this folder's contents; otherwise false.
375     */
376    public void delete(boolean recursive) {
377        URL url = DELETE_FOLDER_URL.buildAlpha(this.getAPI().getBaseURL(), this.getID(), recursive);
378        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE");
379        BoxAPIResponse response = request.send();
380        response.disconnect();
381    }
382
383    @Override
384    public BoxItem.Info move(BoxFolder destination) {
385        return this.move(destination, null);
386    }
387
388    @Override
389    public BoxItem.Info move(BoxFolder destination, String newName) {
390        URL url = FOLDER_INFO_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
391        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
392
393        JsonObject parent = new JsonObject();
394        parent.add("id", destination.getID());
395
396        JsonObject updateInfo = new JsonObject();
397        updateInfo.add("parent", parent);
398        if (newName != null) {
399            updateInfo.add("name", newName);
400        }
401
402        request.setBody(updateInfo.toString());
403        BoxJSONResponse response = (BoxJSONResponse) request.send();
404        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
405        BoxFolder movedFolder = new BoxFolder(this.getAPI(), responseJSON.get("id").asString());
406        return movedFolder.new Info(responseJSON);
407    }
408
409    /**
410     * Renames this folder.
411     *
412     * @param newName the new name of the folder.
413     */
414    public void rename(String newName) {
415        URL url = FOLDER_INFO_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
416        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
417
418        JsonObject updateInfo = new JsonObject();
419        updateInfo.add("name", newName);
420
421        request.setBody(updateInfo.toString());
422        BoxJSONResponse response = (BoxJSONResponse) request.send();
423        response.getJSON();
424    }
425
426    /**
427     * Checks if the file can be successfully uploaded by using the preflight check.
428     *
429     * @param name     the name to give the uploaded file.
430     * @param fileSize the size of the file used for account capacity calculations.
431     */
432    public void canUpload(String name, long fileSize) {
433        URL url = UPLOAD_FILE_URL.build(this.getAPI().getBaseURL());
434        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "OPTIONS");
435
436        JsonObject parent = new JsonObject();
437        parent.add("id", this.getID());
438
439        JsonObject preflightInfo = new JsonObject();
440        preflightInfo.add("parent", parent);
441        preflightInfo.add("name", name);
442
443        preflightInfo.add("size", fileSize);
444
445        request.setBody(preflightInfo.toString());
446        BoxAPIResponse response = request.send();
447        response.disconnect();
448    }
449
450    /**
451     * Uploads a new file to this folder.
452     *
453     * @param fileContent a stream containing the contents of the file to upload.
454     * @param name        the name to give the uploaded file.
455     * @return the uploaded file's info.
456     */
457    public BoxFile.Info uploadFile(InputStream fileContent, String name) {
458        FileUploadParams uploadInfo = new FileUploadParams()
459                .setContent(fileContent)
460                .setName(name);
461        return this.uploadFile(uploadInfo);
462    }
463
464    /**
465     * Uploads a new file to this folder.
466     *
467     * @param callback the callback which allows file content to be written on output stream.
468     * @param name     the name to give the uploaded file.
469     * @return the uploaded file's info.
470     */
471    public BoxFile.Info uploadFile(UploadFileCallback callback, String name) {
472        FileUploadParams uploadInfo = new FileUploadParams()
473                .setUploadFileCallback(callback)
474                .setName(name);
475        return this.uploadFile(uploadInfo);
476    }
477
478    /**
479     * Uploads a new file to this folder while reporting the progress to a ProgressListener.
480     *
481     * @param fileContent a stream containing the contents of the file to upload.
482     * @param name        the name to give the uploaded file.
483     * @param fileSize    the size of the file used for determining the progress of the upload.
484     * @param listener    a listener for monitoring the upload's progress.
485     * @return the uploaded file's info.
486     */
487    public BoxFile.Info uploadFile(InputStream fileContent, String name, long fileSize, ProgressListener listener) {
488        FileUploadParams uploadInfo = new FileUploadParams()
489                .setContent(fileContent)
490                .setName(name)
491                .setSize(fileSize)
492                .setProgressListener(listener);
493        return this.uploadFile(uploadInfo);
494    }
495
496    /**
497     * Uploads a new file to this folder with a specified file description.
498     *
499     * @param fileContent a stream containing the contents of the file to upload.
500     * @param name        the name to give the uploaded file.
501     * @param description the description to give the uploaded file.
502     * @return the uploaded file's info.
503     */
504    public BoxFile.Info uploadFile(InputStream fileContent, String name, String description) {
505        FileUploadParams uploadInfo = new FileUploadParams()
506                .setContent(fileContent)
507                .setName(name)
508                .setDescription(description);
509        return this.uploadFile(uploadInfo);
510    }
511
512    /**
513     * Uploads a new file to this folder with custom upload parameters.
514     *
515     * @param uploadParams the custom upload parameters.
516     * @return the uploaded file's info.
517     */
518    public BoxFile.Info uploadFile(FileUploadParams uploadParams) {
519        URL uploadURL = UPLOAD_FILE_URL.build(this.getAPI().getBaseUploadURL());
520        BoxMultipartRequest request = new BoxMultipartRequest(getAPI(), uploadURL);
521
522        JsonObject fieldJSON = new JsonObject();
523        JsonObject parentIdJSON = new JsonObject();
524        parentIdJSON.add("id", getID());
525        fieldJSON.add("name", uploadParams.getName());
526        fieldJSON.add("parent", parentIdJSON);
527
528        if (uploadParams.getCreated() != null) {
529            fieldJSON.add("content_created_at", BoxDateFormat.format(uploadParams.getCreated()));
530        }
531
532        if (uploadParams.getModified() != null) {
533            fieldJSON.add("content_modified_at", BoxDateFormat.format(uploadParams.getModified()));
534        }
535
536        if (uploadParams.getSHA1() != null && !uploadParams.getSHA1().isEmpty()) {
537            request.setContentSHA1(uploadParams.getSHA1());
538        }
539
540        if (uploadParams.getDescription() != null) {
541            fieldJSON.add("description", uploadParams.getDescription());
542        }
543
544        request.putField("attributes", fieldJSON.toString());
545
546        if (uploadParams.getSize() > 0) {
547            request.setFile(uploadParams.getContent(), uploadParams.getName(), uploadParams.getSize());
548        } else if (uploadParams.getContent() != null) {
549            request.setFile(uploadParams.getContent(), uploadParams.getName());
550        } else {
551            request.setUploadFileCallback(uploadParams.getUploadFileCallback(), uploadParams.getName());
552        }
553
554        BoxJSONResponse response;
555        if (uploadParams.getProgressListener() == null) {
556            response = (BoxJSONResponse) request.send();
557        } else {
558            response = (BoxJSONResponse) request.send(uploadParams.getProgressListener());
559        }
560        JsonObject collection = JsonObject.readFrom(response.getJSON());
561        JsonArray entries = collection.get("entries").asArray();
562        JsonObject fileInfoJSON = entries.get(0).asObject();
563        String uploadedFileID = fileInfoJSON.get("id").asString();
564
565        BoxFile uploadedFile = new BoxFile(getAPI(), uploadedFileID);
566        return uploadedFile.new Info(fileInfoJSON);
567    }
568
569    /**
570     * Uploads a new weblink to this folder.
571     *
572     * @param linkURL the URL the weblink points to.
573     * @return the uploaded weblink's info.
574     */
575    public BoxWebLink.Info createWebLink(URL linkURL) {
576        return this.createWebLink(null, linkURL,
577                null);
578    }
579
580    /**
581     * Uploads a new weblink to this folder.
582     *
583     * @param name    the filename for the weblink.
584     * @param linkURL the URL the weblink points to.
585     * @return the uploaded weblink's info.
586     */
587    public BoxWebLink.Info createWebLink(String name, URL linkURL) {
588        return this.createWebLink(name, linkURL,
589                null);
590    }
591
592    /**
593     * Uploads a new weblink to this folder.
594     *
595     * @param linkURL     the URL the weblink points to.
596     * @param description the weblink's description.
597     * @return the uploaded weblink's info.
598     */
599    public BoxWebLink.Info createWebLink(URL linkURL, String description) {
600        return this.createWebLink(null, linkURL, description);
601    }
602
603    /**
604     * Uploads a new weblink to this folder.
605     *
606     * @param name        the filename for the weblink.
607     * @param linkURL     the URL the weblink points to.
608     * @param description the weblink's description.
609     * @return the uploaded weblink's info.
610     */
611    public BoxWebLink.Info createWebLink(String name, URL linkURL, String description) {
612        JsonObject parent = new JsonObject();
613        parent.add("id", this.getID());
614
615        JsonObject newWebLink = new JsonObject();
616        newWebLink.add("name", name);
617        newWebLink.add("parent", parent);
618        newWebLink.add("url", linkURL.toString());
619
620        if (description != null) {
621            newWebLink.add("description", description);
622        }
623
624        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(),
625                CREATE_WEB_LINK_URL.build(this.getAPI().getBaseURL()), "POST");
626        request.setBody(newWebLink.toString());
627        BoxJSONResponse response = (BoxJSONResponse) request.send();
628        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
629
630        BoxWebLink createdWebLink = new BoxWebLink(this.getAPI(), responseJSON.get("id").asString());
631        return createdWebLink.new Info(responseJSON);
632    }
633
634    /**
635     * Returns an iterable containing the items in this folder. Iterating over the iterable returned by this method is
636     * equivalent to iterating over this BoxFolder directly.
637     *
638     * @return an iterable containing the items in this folder.
639     */
640    public Iterable<BoxItem.Info> getChildren() {
641        return this;
642    }
643
644    /**
645     * Returns an iterable containing the items in this folder and specifies which child fields to retrieve from the
646     * API.
647     *
648     * @param fields the fields to retrieve.
649     * @return an iterable containing the items in this folder.
650     */
651    public Iterable<BoxItem.Info> getChildren(final String... fields) {
652        return new Iterable<BoxItem.Info>() {
653            @Override
654            public Iterator<BoxItem.Info> iterator() {
655                String queryString = new QueryStringBuilder().appendParam("fields", fields).toString();
656                URL url = GET_ITEMS_URL.buildWithQuery(getAPI().getBaseURL(), queryString, getID());
657                return new BoxItemIterator(getAPI(), url);
658            }
659        };
660    }
661
662    /**
663     * Returns an iterable containing the items in this folder sorted by name and direction.
664     * @param sort the field to sort by, can be set as `name`, `id`, and `date`.
665     * @param direction the direction to display the item results.
666     * @param fields the fields to retrieve.
667     * @return an iterable containing the items in this folder.
668     */
669    public Iterable<BoxItem.Info> getChildren(String sort, SortDirection direction, final String... fields) {
670        QueryStringBuilder builder = new QueryStringBuilder()
671                .appendParam("sort", sort)
672                .appendParam("direction", direction.toString());
673
674        if (fields.length > 0) {
675            builder.appendParam("fields", fields).toString();
676        }
677        final String query = builder.toString();
678        return new Iterable<BoxItem.Info>() {
679            @Override
680            public Iterator<BoxItem.Info> iterator() {
681                URL url = GET_ITEMS_URL.buildWithQuery(getAPI().getBaseURL(), query, getID());
682                return new BoxItemIterator(getAPI(), url);
683            }
684        };
685    }
686
687    /**
688     * Returns an iterable containing the items in this folder sorted by name and direction.
689     * @param sort the field to sort by, can be set as `name`, `id`, and `date`.
690     * @param direction the direction to display the item results.
691     * @param offset the index of the first child item to retrieve.
692     * @param limit  the maximum number of children to retrieve after the offset.
693     * @param fields the fields to retrieve.
694     * @return an iterable containing the items in this folder.
695     */
696    public Iterable<BoxItem.Info> getChildren(String sort, SortDirection direction, final long offset, final long limit,
697                                              final String... fields) {
698        QueryStringBuilder builder = new QueryStringBuilder()
699                .appendParam("sort", sort)
700                .appendParam("direction", direction.toString());
701
702        if (fields.length > 0) {
703            builder.appendParam("fields", fields).toString();
704        }
705        final String query = builder.toString();
706        return new Iterable<BoxItem.Info>() {
707            @Override
708            public Iterator<BoxItem.Info> iterator() {
709                URL url = GET_ITEMS_URL.buildWithQuery(getAPI().getBaseURL(), query, getID());
710                return new BoxItemIterator(getAPI(), url, limit, offset);
711            }
712        };
713    }
714
715    /**
716     * Retrieves a specific range of child items in this folder.
717     *
718     * @param offset the index of the first child item to retrieve.
719     * @param limit  the maximum number of children to retrieve after the offset.
720     * @param fields the fields to retrieve.
721     * @return a partial collection containing the specified range of child items.
722     */
723    public PartialCollection<BoxItem.Info> getChildrenRange(long offset, long limit, String... fields) {
724        QueryStringBuilder builder = new QueryStringBuilder()
725                .appendParam("limit", limit)
726                .appendParam("offset", offset);
727
728        if (fields.length > 0) {
729            builder.appendParam("fields", fields).toString();
730        }
731
732        URL url = GET_ITEMS_URL.buildWithQuery(getAPI().getBaseURL(), builder.toString(), getID());
733        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
734        BoxJSONResponse response = (BoxJSONResponse) request.send();
735        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
736
737        String totalCountString = responseJSON.get("total_count").toString();
738        long fullSize = Double.valueOf(totalCountString).longValue();
739        PartialCollection<BoxItem.Info> children = new PartialCollection<BoxItem.Info>(offset, limit, fullSize);
740        JsonArray jsonArray = responseJSON.get("entries").asArray();
741        for (JsonValue value : jsonArray) {
742            JsonObject jsonObject = value.asObject();
743            BoxItem.Info parsedItemInfo = (BoxItem.Info) BoxResource.parseInfo(this.getAPI(), jsonObject);
744            if (parsedItemInfo != null) {
745                children.add(parsedItemInfo);
746            }
747        }
748        return children;
749    }
750
751    /**
752     * Returns an iterator over the items in this folder.
753     *
754     * @return an iterator over the items in this folder.
755     */
756    @Override
757    public Iterator<BoxItem.Info> iterator() {
758        URL url = GET_ITEMS_URL.build(this.getAPI().getBaseURL(), BoxFolder.this.getID());
759        return new BoxItemIterator(BoxFolder.this.getAPI(), url);
760    }
761
762    /**
763     * Adds new {@link BoxWebHook} to this {@link BoxFolder}.
764     *
765     * @param address  {@link BoxWebHook.Info#getAddress()}
766     * @param triggers {@link BoxWebHook.Info#getTriggers()}
767     * @return created {@link BoxWebHook.Info}
768     */
769    public BoxWebHook.Info addWebHook(URL address, BoxWebHook.Trigger... triggers) {
770        return BoxWebHook.create(this, address, triggers);
771    }
772
773    /**
774     * Used to retrieve the watermark for the folder.
775     * If the folder does not have a watermark applied to it, a 404 Not Found will be returned by API.
776     *
777     * @param fields the fields to retrieve.
778     * @return the watermark associated with the folder.
779     */
780    public BoxWatermark getWatermark(String... fields) {
781        return this.getWatermark(FOLDER_INFO_URL_TEMPLATE, fields);
782    }
783
784    /**
785     * Used to apply or update the watermark for the folder.
786     *
787     * @return the watermark associated with the folder.
788     */
789    public BoxWatermark applyWatermark() {
790        return this.applyWatermark(FOLDER_INFO_URL_TEMPLATE, BoxWatermark.WATERMARK_DEFAULT_IMPRINT);
791    }
792
793    /**
794     * Removes a watermark from the folder.
795     * If the folder did not have a watermark applied to it, a 404 Not Found will be returned by API.
796     */
797    public void removeWatermark() {
798        this.removeWatermark(FOLDER_INFO_URL_TEMPLATE);
799    }
800
801    /**
802     * Used to retrieve all metadata associated with the folder.
803     *
804     * @param fields the optional fields to retrieve.
805     * @return An iterable of metadata instances associated with the folder
806     */
807    public Iterable<Metadata> getAllMetadata(String... fields) {
808        return Metadata.getAllMetadata(this, fields);
809    }
810
811    /**
812     * This method is deprecated, please use the {@link BoxSearch} class instead.
813     * Searches this folder and all descendant folders using a given queryPlease use BoxSearch Instead.
814     *
815     * @param query the search query.
816     * @return an Iterable containing the search results.
817     */
818    @Deprecated
819    public Iterable<BoxItem.Info> search(final String query) {
820        return new Iterable<BoxItem.Info>() {
821            @Override
822            public Iterator<BoxItem.Info> iterator() {
823                QueryStringBuilder builder = new QueryStringBuilder();
824                builder.appendParam("query", query);
825                builder.appendParam("ancestor_folder_ids", getID());
826
827                URL url = SEARCH_URL_TEMPLATE.buildWithQuery(getAPI().getBaseURL(), builder.toString());
828                return new BoxItemIterator(getAPI(), url);
829            }
830        };
831    }
832
833    @Override
834    public BoxFolder.Info setCollections(BoxCollection... collections) {
835        JsonArray jsonArray = new JsonArray();
836        for (BoxCollection collection : collections) {
837            JsonObject collectionJSON = new JsonObject();
838            collectionJSON.add("id", collection.getID());
839            jsonArray.add(collectionJSON);
840        }
841        JsonObject infoJSON = new JsonObject();
842        infoJSON.add("collections", jsonArray);
843
844        String queryString = new QueryStringBuilder().appendParam("fields", ALL_FIELDS).toString();
845        URL url = FOLDER_INFO_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
846        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
847        request.setBody(infoJSON.toString());
848        BoxJSONResponse response = (BoxJSONResponse) request.send();
849        JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
850        return new Info(jsonObject);
851    }
852
853    /**
854     * Creates global property metadata on this folder.
855     *
856     * @param metadata the new metadata values.
857     * @return the metadata returned from the server.
858     */
859    public Metadata createMetadata(Metadata metadata) {
860        return this.createMetadata(Metadata.DEFAULT_METADATA_TYPE, metadata);
861    }
862
863    /**
864     * Creates metadata on this folder using a specified template.
865     *
866     * @param templateName the name of the metadata template.
867     * @param metadata     the new metadata values.
868     * @return the metadata returned from the server.
869     */
870    public Metadata createMetadata(String templateName, Metadata metadata) {
871        String scope = Metadata.scopeBasedOnType(templateName);
872        return this.createMetadata(templateName, scope, metadata);
873    }
874
875    /**
876     * Creates metadata on this folder using a specified scope and template.
877     *
878     * @param templateName the name of the metadata template.
879     * @param scope        the scope of the template (usually "global" or "enterprise").
880     * @param metadata     the new metadata values.
881     * @return the metadata returned from the server.
882     */
883    public Metadata createMetadata(String templateName, String scope, Metadata metadata) {
884        URL url = METADATA_URL_TEMPLATE.buildAlpha(this.getAPI().getBaseURL(), this.getID(), scope, templateName);
885        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "POST");
886        request.addHeader("Content-Type", "application/json");
887        request.setBody(metadata.toString());
888        BoxJSONResponse response = (BoxJSONResponse) request.send();
889        return new Metadata(JsonObject.readFrom(response.getJSON()));
890    }
891
892    /**
893     * Sets the provided metadata on the folder, overwriting any existing metadata keys already present.
894     *
895     * @param templateName the name of the metadata template.
896     * @param scope        the scope of the template (usually "global" or "enterprise").
897     * @param metadata     the new metadata values.
898     * @return the metadata returned from the server.
899     */
900    public Metadata setMetadata(String templateName, String scope, Metadata metadata) {
901        Metadata metadataValue = null;
902
903        try {
904            metadataValue = this.createMetadata(templateName, scope, metadata);
905        } catch (BoxAPIException e) {
906            if (e.getResponseCode() == 409) {
907                Metadata metadataToUpdate = new Metadata(scope, templateName);
908                for (JsonValue value : metadata.getOperations()) {
909                    if (value.asObject().get("value").isNumber()) {
910                        metadataToUpdate.add(value.asObject().get("path").asString(),
911                                value.asObject().get("value").asDouble());
912                    } else if (value.asObject().get("value").isString()) {
913                        metadataToUpdate.add(value.asObject().get("path").asString(),
914                                value.asObject().get("value").asString());
915                    } else if (value.asObject().get("value").isArray()) {
916                        ArrayList<String> list = new ArrayList<String>();
917                        for (JsonValue jsonValue : value.asObject().get("value").asArray()) {
918                            list.add(jsonValue.asString());
919                        }
920                        metadataToUpdate.add(value.asObject().get("path").asString(), list);
921                    }
922                }
923                metadataValue = this.updateMetadata(metadataToUpdate);
924            } else {
925                throw e;
926            }
927        }
928
929        return metadataValue;
930    }
931
932    /**
933     * Gets the global properties metadata on this folder.
934     *
935     * @return the metadata returned from the server.
936     */
937    public Metadata getMetadata() {
938        return this.getMetadata(Metadata.DEFAULT_METADATA_TYPE);
939    }
940
941    /**
942     * Gets the metadata on this folder associated with a specified template.
943     *
944     * @param templateName the metadata template type name.
945     * @return the metadata returned from the server.
946     */
947    public Metadata getMetadata(String templateName) {
948        String scope = Metadata.scopeBasedOnType(templateName);
949        return this.getMetadata(templateName, scope);
950    }
951
952    /**
953     * Gets the metadata on this folder associated with a specified scope and template.
954     *
955     * @param templateName the metadata template type name.
956     * @param scope        the scope of the template (usually "global" or "enterprise").
957     * @return the metadata returned from the server.
958     */
959    public Metadata getMetadata(String templateName, String scope) {
960        URL url = METADATA_URL_TEMPLATE.buildAlpha(this.getAPI().getBaseURL(), this.getID(), scope, templateName);
961        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
962        BoxJSONResponse response = (BoxJSONResponse) request.send();
963        return new Metadata(JsonObject.readFrom(response.getJSON()));
964    }
965
966    /**
967     * Updates the global properties metadata on this folder.
968     *
969     * @param metadata the new metadata values.
970     * @return the metadata returned from the server.
971     */
972    public Metadata updateMetadata(Metadata metadata) {
973        URL url = METADATA_URL_TEMPLATE.buildAlpha(this.getAPI().getBaseURL(), this.getID(), metadata.getScope(),
974                metadata.getTemplateName());
975        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "PUT");
976        request.addHeader("Content-Type", "application/json-patch+json");
977        request.setBody(metadata.getPatch());
978        BoxJSONResponse response = (BoxJSONResponse) request.send();
979        return new Metadata(JsonObject.readFrom(response.getJSON()));
980    }
981
982    /**
983     * Deletes the global properties metadata on this folder.
984     */
985    public void deleteMetadata() {
986        this.deleteMetadata(Metadata.DEFAULT_METADATA_TYPE);
987    }
988
989    /**
990     * Deletes the metadata on this folder associated with a specified template.
991     *
992     * @param templateName the metadata template type name.
993     */
994    public void deleteMetadata(String templateName) {
995        String scope = Metadata.scopeBasedOnType(templateName);
996        this.deleteMetadata(templateName, scope);
997    }
998
999    /**
1000     * Deletes the metadata on this folder associated with a specified scope and template.
1001     *
1002     * @param templateName the metadata template type name.
1003     * @param scope        the scope of the template (usually "global" or "enterprise").
1004     */
1005    public void deleteMetadata(String templateName, String scope) {
1006        URL url = METADATA_URL_TEMPLATE.buildAlpha(this.getAPI().getBaseURL(), this.getID(), scope, templateName);
1007        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE");
1008        BoxAPIResponse response = request.send();
1009        response.disconnect();
1010    }
1011
1012    /**
1013     * Adds a metadata classification to the specified file.
1014     *
1015     * @param classificationType the metadata classification type.
1016     * @return the metadata classification type added to the file.
1017     */
1018    public String addClassification(String classificationType) {
1019        Metadata metadata = new Metadata().add(Metadata.CLASSIFICATION_KEY, classificationType);
1020        Metadata classification = this.createMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY,
1021                "enterprise", metadata);
1022
1023        return classification.getString(Metadata.CLASSIFICATION_KEY);
1024    }
1025
1026    /**
1027     * Updates a metadata classification on the specified file.
1028     *
1029     * @param classificationType the metadata classification type.
1030     * @return the new metadata classification type updated on the file.
1031     */
1032    public String updateClassification(String classificationType) {
1033        Metadata metadata = new Metadata("enterprise", Metadata.CLASSIFICATION_TEMPLATE_KEY);
1034        metadata.replace(Metadata.CLASSIFICATION_KEY, classificationType);
1035        Metadata classification = this.updateMetadata(metadata);
1036
1037        return classification.getString(Metadata.CLASSIFICATION_KEY);
1038    }
1039
1040    /**
1041     * Attempts to add classification to a file. If classification already exists then do update.
1042     *
1043     * @param classificationType the metadata classification type.
1044     * @return the metadata classification type on the file.
1045     */
1046    public String setClassification(String classificationType) {
1047        Metadata metadata = new Metadata().add(Metadata.CLASSIFICATION_KEY, classificationType);
1048        Metadata classification = null;
1049
1050        try {
1051            classification = this.createMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY, "enterprise", metadata);
1052        } catch (BoxAPIException e) {
1053            if (e.getResponseCode() == 409) {
1054                metadata = new Metadata("enterprise", Metadata.CLASSIFICATION_TEMPLATE_KEY);
1055                metadata.replace(Metadata.CLASSIFICATION_KEY, classificationType);
1056                classification = this.updateMetadata(metadata);
1057            } else {
1058                throw e;
1059            }
1060        }
1061
1062        return classification.getString("/Box__Security__Classification__Key");
1063    }
1064
1065    /**
1066     * Gets the classification type for the specified file.
1067     *
1068     * @return the metadata classification type on the file.
1069     */
1070    public String getClassification() {
1071        Metadata metadata = this.getMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY);
1072        return metadata.getString(Metadata.CLASSIFICATION_KEY);
1073    }
1074
1075    /**
1076     * Deletes the classification on the file.
1077     */
1078    public void deleteClassification() {
1079        this.deleteMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY, "enterprise");
1080    }
1081
1082    /**
1083     * Creates an upload session to create a new file in chunks.
1084     * This will first verify that the file can be created and then open a session for uploading pieces of the file.
1085     *
1086     * @param fileName the name of the file to be created
1087     * @param fileSize the size of the file that will be uploaded
1088     * @return the created upload session instance
1089     */
1090    public BoxFileUploadSession.Info createUploadSession(String fileName, long fileSize) {
1091
1092        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL());
1093        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
1094
1095        JsonObject body = new JsonObject();
1096        body.add("folder_id", this.getID());
1097        body.add("file_name", fileName);
1098        body.add("file_size", fileSize);
1099        request.setBody(body.toString());
1100
1101        BoxJSONResponse response = (BoxJSONResponse) request.send();
1102        JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
1103
1104        String sessionId = jsonObject.get("id").asString();
1105        BoxFileUploadSession session = new BoxFileUploadSession(this.getAPI(), sessionId);
1106
1107        return session.new Info(jsonObject);
1108    }
1109
1110    /**
1111     * Creates a new file.
1112     *
1113     * @param inputStream the stream instance that contains the data.
1114     * @param fileName    the name of the file to be created.
1115     * @param fileSize    the size of the file that will be uploaded.
1116     * @return the created file instance.
1117     * @throws InterruptedException when a thread execution is interrupted.
1118     * @throws IOException          when reading a stream throws exception.
1119     */
1120    public BoxFile.Info uploadLargeFile(InputStream inputStream, String fileName, long fileSize)
1121            throws InterruptedException, IOException {
1122        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL());
1123        this.canUpload(fileName, fileSize);
1124        return new LargeFileUpload().
1125                upload(this.getAPI(), this.getID(), inputStream, url, fileName, fileSize);
1126    }
1127
1128    /**
1129     * Creates a new file.  Also sets file attributes.
1130     *
1131     * @param inputStream the stream instance that contains the data.
1132     * @param fileName    the name of the file to be created.
1133     * @param fileSize    the size of the file that will be uploaded.
1134     * @param fileAttributes file attributes to set
1135     * @return the created file instance.
1136     * @throws InterruptedException when a thread execution is interrupted.
1137     * @throws IOException          when reading a stream throws exception.
1138     */
1139    public BoxFile.Info uploadLargeFile(InputStream inputStream, String fileName, long fileSize,
1140            Map<String, String> fileAttributes)
1141            throws InterruptedException, IOException {
1142        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL());
1143        this.canUpload(fileName, fileSize);
1144        return new LargeFileUpload().
1145                upload(this.getAPI(), this.getID(), inputStream, url, fileName, fileSize, fileAttributes);
1146    }
1147
1148    /**
1149     * Creates a new file using specified number of parallel http connections.
1150     *
1151     * @param inputStream          the stream instance that contains the data.
1152     * @param fileName             the name of the file to be created.
1153     * @param fileSize             the size of the file that will be uploaded.
1154     * @param nParallelConnections number of parallel http connections to use
1155     * @param timeOut              time to wait before killing the job
1156     * @param unit                 time unit for the time wait value
1157     * @return the created file instance.
1158     * @throws InterruptedException when a thread execution is interrupted.
1159     * @throws IOException          when reading a stream throws exception.
1160     */
1161    public BoxFile.Info uploadLargeFile(InputStream inputStream, String fileName, long fileSize,
1162                                        int nParallelConnections, long timeOut, TimeUnit unit)
1163            throws InterruptedException, IOException {
1164        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL());
1165        this.canUpload(fileName, fileSize);
1166        return new LargeFileUpload(nParallelConnections, timeOut, unit).
1167                upload(this.getAPI(), this.getID(), inputStream, url, fileName, fileSize);
1168    }
1169
1170    /**
1171     * Creates a new file using specified number of parallel http connections.  Also sets file attributes.
1172     *
1173     * @param inputStream          the stream instance that contains the data.
1174     * @param fileName             the name of the file to be created.
1175     * @param fileSize             the size of the file that will be uploaded.
1176     * @param nParallelConnections number of parallel http connections to use
1177     * @param timeOut              time to wait before killing the job
1178     * @param unit                 time unit for the time wait value
1179     * @param fileAttributes       file attributes to set
1180     * @return the created file instance.
1181     * @throws InterruptedException when a thread execution is interrupted.
1182     * @throws IOException          when reading a stream throws exception.
1183     */
1184    public BoxFile.Info uploadLargeFile(InputStream inputStream, String fileName, long fileSize,
1185                                        int nParallelConnections, long timeOut, TimeUnit unit,
1186                                        Map<String, String> fileAttributes)
1187            throws InterruptedException, IOException {
1188        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL());
1189        this.canUpload(fileName, fileSize);
1190        return new LargeFileUpload(nParallelConnections, timeOut, unit).
1191                upload(this.getAPI(), this.getID(), inputStream, url, fileName, fileSize, fileAttributes);
1192    }
1193
1194    /**
1195     * Creates a new Metadata Cascade Policy on a folder.
1196     *
1197     * @param scope         the scope of the metadata cascade policy.
1198     * @param templateKey   the key of the template.
1199     * @return  information about the Metadata Cascade Policy.
1200     */
1201    public BoxMetadataCascadePolicy.Info addMetadataCascadePolicy(String scope, String templateKey) {
1202
1203        return BoxMetadataCascadePolicy.create(this.getAPI(), this.getID(), scope, templateKey);
1204    }
1205
1206    /**
1207     * Retrieves all Metadata Cascade Policies on a folder.
1208     *
1209     * @param fields            optional fields to retrieve for cascade policies.
1210     * @return  the Iterable of Box Metadata Cascade Policies in your enterprise.
1211     */
1212    public Iterable<BoxMetadataCascadePolicy.Info> getMetadataCascadePolicies(String... fields) {
1213        Iterable<BoxMetadataCascadePolicy.Info> cascadePoliciesInfo =
1214                BoxMetadataCascadePolicy.getAll(this.getAPI(), this.getID(), fields);
1215
1216        return cascadePoliciesInfo;
1217    }
1218
1219    /**
1220     * Retrieves all Metadata Cascade Policies on a folder.
1221     *
1222     * @param enterpriseID      the ID of the enterprise to retrieve cascade policies for.
1223     * @param limit             the number of entries of cascade policies to retrieve.
1224     * @param fields            optional fields to retrieve for cascade policies.
1225     * @return  the Iterable of Box Metadata Cascade Policies in your enterprise.
1226     */
1227    public Iterable<BoxMetadataCascadePolicy.Info> getMetadataCascadePolicies(String enterpriseID,
1228                                                                      int limit, String... fields) {
1229        Iterable<BoxMetadataCascadePolicy.Info> cascadePoliciesInfo =
1230                BoxMetadataCascadePolicy.getAll(this.getAPI(), this.getID(), enterpriseID, limit, fields);
1231
1232        return cascadePoliciesInfo;
1233    }
1234
1235    /**
1236     * Lock this folder.
1237     *
1238     * @return a created folder lock object.
1239     */
1240    public BoxFolderLock.Info lock() {
1241        JsonObject folderObject = new JsonObject();
1242        folderObject.add("type", "folder");
1243        folderObject.add("id", this.getID());
1244
1245        JsonObject lockedOperations = new JsonObject();
1246        lockedOperations.add("move", true);
1247        lockedOperations.add("delete", true);
1248
1249
1250        JsonObject body = new JsonObject();
1251        body.add("folder", folderObject);
1252        body.add("locked_operations", lockedOperations);
1253
1254        BoxJSONRequest request =
1255            new BoxJSONRequest(this.getAPI(), FOLDER_LOCK_URL_TEMPLATE.build(this.getAPI().getBaseURL()),
1256                "POST");
1257        request.setBody(body.toString());
1258        BoxJSONResponse response = (BoxJSONResponse) request.send();
1259        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
1260
1261        BoxFolderLock createdFolderLock = new BoxFolderLock(this.getAPI(), responseJSON.get("id").asString());
1262        return createdFolderLock.new Info(responseJSON);
1263    }
1264
1265    /**
1266     * Get the lock on this folder.
1267     *
1268     * @return a folder lock object.
1269     */
1270    public Iterable<BoxFolderLock.Info> getLocks() {
1271        String queryString = new QueryStringBuilder().appendParam("folder_id", this.getID()).toString();
1272        final BoxAPIConnection api = this.getAPI();
1273        return new BoxResourceIterable<BoxFolderLock.Info>(api,
1274            FOLDER_LOCK_URL_TEMPLATE.buildWithQuery(api.getBaseURL(), queryString), 100) {
1275            @Override
1276            protected BoxFolderLock.Info factory(JsonObject jsonObject) {
1277                BoxFolderLock folderLock =
1278                        new BoxFolderLock(api, jsonObject.get("id").asString());
1279                return folderLock.new Info(jsonObject);
1280            }
1281        };
1282    }
1283
1284    /**
1285     * Contains information about a BoxFolder.
1286     */
1287    public class Info extends BoxItem.Info {
1288        private BoxUploadEmail uploadEmail;
1289        private boolean hasCollaborations;
1290        private SyncState syncState;
1291        private EnumSet<Permission> permissions;
1292        private boolean canNonOwnersInvite;
1293        private boolean isWatermarked;
1294        private boolean isCollaborationRestrictedToEnterprise;
1295        private boolean isExternallyOwned;
1296        private Map<String, Map<String, Metadata>> metadataMap;
1297        private List<String> allowedSharedLinkAccessLevels;
1298        private List<String> allowedInviteeRoles;
1299        private BoxClassification classification;
1300
1301        /**
1302         * Constructs an empty Info object.
1303         */
1304        public Info() {
1305            super();
1306        }
1307
1308        /**
1309         * Constructs an Info object by parsing information from a JSON string.
1310         *
1311         * @param json the JSON string to parse.
1312         */
1313        public Info(String json) {
1314            super(json);
1315        }
1316
1317        /**
1318         * Constructs an Info object using an already parsed JSON object.
1319         *
1320         * @param jsonObject the parsed JSON object.
1321         */
1322        public Info(JsonObject jsonObject) {
1323            super(jsonObject);
1324        }
1325
1326        /**
1327         * Gets the upload email for the folder.
1328         *
1329         * @return the upload email for the folder.
1330         */
1331        public BoxUploadEmail getUploadEmail() {
1332            return this.uploadEmail;
1333        }
1334
1335        /**
1336         * Sets the upload email for the folder.
1337         *
1338         * @param uploadEmail the upload email for the folder.
1339         */
1340        public void setUploadEmail(BoxUploadEmail uploadEmail) {
1341            if (this.uploadEmail == uploadEmail) {
1342                return;
1343            }
1344
1345            this.removeChildObject("folder_upload_email");
1346            this.uploadEmail = uploadEmail;
1347
1348            if (uploadEmail == null) {
1349                this.addPendingChange("folder_upload_email", (String) null);
1350            } else {
1351                this.addChildObject("folder_upload_email", uploadEmail);
1352            }
1353        }
1354
1355        /**
1356         * Gets whether or not the folder has any collaborations.
1357         *
1358         * @return true if the folder has collaborations; otherwise false.
1359         */
1360        public boolean getHasCollaborations() {
1361            return this.hasCollaborations;
1362        }
1363
1364        /**
1365         * Gets the sync state of the folder.
1366         *
1367         * @return the sync state of the folder.
1368         */
1369        public SyncState getSyncState() {
1370            return this.syncState;
1371        }
1372
1373        /**
1374         * Sets the sync state of the folder.
1375         *
1376         * @param syncState the sync state of the folder.
1377         */
1378        public void setSyncState(SyncState syncState) {
1379            this.syncState = syncState;
1380            this.addPendingChange("sync_state", syncState.toJSONValue());
1381        }
1382
1383        /**
1384         * Gets the permissions that the current user has on the folder.
1385         *
1386         * @return the permissions that the current user has on the folder.
1387         */
1388        public EnumSet<Permission> getPermissions() {
1389            return this.permissions;
1390        }
1391
1392        /**
1393         * Gets whether or not the non-owners can invite collaborators to the folder.
1394         *
1395         * @return [description]
1396         */
1397        public boolean getCanNonOwnersInvite() {
1398            return this.canNonOwnersInvite;
1399        }
1400
1401        /**
1402         * Sets whether or not non-owners can invite collaborators to the folder.
1403         *
1404         * @param canNonOwnersInvite indicates non-owners can invite collaborators to the folder.
1405         */
1406        public void setCanNonOwnersInvite(boolean canNonOwnersInvite) {
1407            this.canNonOwnersInvite = canNonOwnersInvite;
1408            this.addPendingChange("can_non_owners_invite", canNonOwnersInvite);
1409        }
1410
1411        /**
1412         * Gets whether future collaborations should be restricted to within the enterprise only.
1413         *
1414         * @return indicates whether collaboration is restricted to enterprise only.
1415         */
1416        public boolean getIsCollaborationRestrictedToEnterprise() {
1417            return this.isCollaborationRestrictedToEnterprise;
1418        }
1419
1420        /**
1421         * Sets whether future collaborations should be restricted to within the enterprise only.
1422         *
1423         * @param isRestricted indicates whether there is collaboration restriction within enterprise.
1424         */
1425        public void setIsCollaborationRestrictedToEnterprise(boolean isRestricted) {
1426            this.isCollaborationRestrictedToEnterprise = isRestricted;
1427            this.addPendingChange("is_collaboration_restricted_to_enterprise", isRestricted);
1428        }
1429
1430        /**
1431         * Retrieves the allowed roles for collaborations.
1432         *
1433         * @return the roles allowed for collaboration.
1434         */
1435        public List<String> getAllowedInviteeRoles() {
1436            return this.allowedInviteeRoles;
1437        }
1438
1439        /**
1440         * Retrieves the allowed access levels for a shared link.
1441         *
1442         * @return the allowed access levels for a shared link.
1443         */
1444        public List<String> getAllowedSharedLinkAccessLevels() {
1445            return this.allowedSharedLinkAccessLevels;
1446        }
1447
1448        /**
1449         * Gets flag indicating whether this file is Watermarked.
1450         *
1451         * @return whether the file is watermarked or not
1452         */
1453        public boolean getIsWatermarked() {
1454            return this.isWatermarked;
1455        }
1456
1457        /**
1458         * Gets the metadata on this folder associated with a specified scope and template.
1459         * Makes an attempt to get metadata that was retrieved using getInfo(String ...) method. If no result is found
1460         * then makes an API call to get metadata
1461         *
1462         * @param templateName the metadata template type name.
1463         * @param scope        the scope of the template (usually "global" or "enterprise").
1464         * @return the metadata returned from the server.
1465         */
1466        public Metadata getMetadata(String templateName, String scope) {
1467            try {
1468                return this.metadataMap.get(scope).get(templateName);
1469            } catch (NullPointerException e) {
1470                return null;
1471            }
1472        }
1473
1474        /**
1475         * Get the field is_externally_owned determining whether this folder is owned by a user outside of the
1476         * enterprise.
1477         * @return a boolean indicating whether this folder is owned by a user outside the enterprise.
1478         */
1479        public boolean getIsExternallyOwned() {
1480            return this.isExternallyOwned;
1481        }
1482
1483        /**
1484         * Gets the metadata classification type of this folder.
1485         * @return the metadata classification type of this folder.
1486         */
1487        public BoxClassification getClassification() {
1488            return this.classification;
1489        }
1490
1491        @Override
1492        public BoxFolder getResource() {
1493            return BoxFolder.this;
1494        }
1495
1496        @Override
1497        protected void parseJSONMember(JsonObject.Member member) {
1498            super.parseJSONMember(member);
1499
1500            String memberName = member.getName();
1501            JsonValue value = member.getValue();
1502            try {
1503                if (memberName.equals("folder_upload_email")) {
1504                    if (this.uploadEmail == null) {
1505                        this.uploadEmail = new BoxUploadEmail(value.asObject());
1506                    } else {
1507                        this.uploadEmail.update(value.asObject());
1508                    }
1509
1510                } else if (memberName.equals("has_collaborations")) {
1511                    this.hasCollaborations = value.asBoolean();
1512
1513                } else if (memberName.equals("sync_state")) {
1514                    this.syncState = SyncState.fromJSONValue(value.asString());
1515
1516                } else if (memberName.equals("permissions")) {
1517                    this.permissions = this.parsePermissions(value.asObject());
1518
1519                } else if (memberName.equals("can_non_owners_invite")) {
1520                    this.canNonOwnersInvite = value.asBoolean();
1521                } else if (memberName.equals("allowed_shared_link_access_levels")) {
1522                    this.allowedSharedLinkAccessLevels = this.parseSharedLinkAccessLevels(value.asArray());
1523                } else if (memberName.equals("allowed_invitee_roles")) {
1524                    this.allowedInviteeRoles = this.parseAllowedInviteeRoles(value.asArray());
1525                } else if (memberName.equals("is_collaboration_restricted_to_enterprise")) {
1526                    this.isCollaborationRestrictedToEnterprise = value.asBoolean();
1527                } else if (memberName.equals("is_externally_owned")) {
1528                    this.isExternallyOwned = value.asBoolean();
1529                } else if (memberName.equals("watermark_info")) {
1530                    JsonObject jsonObject = value.asObject();
1531                    this.isWatermarked = jsonObject.get("is_watermarked").asBoolean();
1532                } else if (memberName.equals("metadata")) {
1533                    JsonObject jsonObject = value.asObject();
1534                    this.metadataMap = Parsers.parseAndPopulateMetadataMap(jsonObject);
1535                } else if (memberName.equals("classification")) {
1536                    if (value.isNull()) {
1537                        this.classification = null;
1538                    } else {
1539                        this.classification = new BoxClassification(value.asObject());
1540                    }
1541                }
1542            } catch (Exception e) {
1543                throw new BoxDeserializationException(memberName, value.toString(), e);
1544            }
1545        }
1546
1547        private EnumSet<Permission> parsePermissions(JsonObject jsonObject) {
1548            EnumSet<Permission> permissions = EnumSet.noneOf(Permission.class);
1549            for (JsonObject.Member member : jsonObject) {
1550                JsonValue value = member.getValue();
1551                if (value.isNull() || !value.asBoolean()) {
1552                    continue;
1553                }
1554
1555                String memberName = member.getName();
1556                if (memberName.equals("can_download")) {
1557                    permissions.add(Permission.CAN_DOWNLOAD);
1558                } else if (memberName.equals("can_upload")) {
1559                    permissions.add(Permission.CAN_UPLOAD);
1560                } else if (memberName.equals("can_rename")) {
1561                    permissions.add(Permission.CAN_RENAME);
1562                } else if (memberName.equals("can_delete")) {
1563                    permissions.add(Permission.CAN_DELETE);
1564                } else if (memberName.equals("can_share")) {
1565                    permissions.add(Permission.CAN_SHARE);
1566                } else if (memberName.equals("can_invite_collaborator")) {
1567                    permissions.add(Permission.CAN_INVITE_COLLABORATOR);
1568                } else if (memberName.equals("can_set_share_access")) {
1569                    permissions.add(Permission.CAN_SET_SHARE_ACCESS);
1570                }
1571            }
1572
1573            return permissions;
1574        }
1575
1576        private List<String> parseSharedLinkAccessLevels(JsonArray jsonArray) {
1577            List<String> accessLevels = new ArrayList<String>(jsonArray.size());
1578            for (JsonValue value : jsonArray) {
1579                accessLevels.add(value.asString());
1580            }
1581
1582            return accessLevels;
1583        }
1584
1585        private List<String> parseAllowedInviteeRoles(JsonArray jsonArray) {
1586            List<String> roles = new ArrayList<String>(jsonArray.size());
1587            for (JsonValue value : jsonArray) {
1588                roles.add(value.asString());
1589            }
1590
1591            return roles;
1592        }
1593    }
1594
1595    /**
1596     * Enumerates the possible sync states that a folder can have.
1597     */
1598    public enum SyncState {
1599        /**
1600         * The folder is synced.
1601         */
1602        SYNCED("synced"),
1603
1604        /**
1605         * The folder is not synced.
1606         */
1607        NOT_SYNCED("not_synced"),
1608
1609        /**
1610         * The folder is partially synced.
1611         */
1612        PARTIALLY_SYNCED("partially_synced");
1613
1614        private final String jsonValue;
1615
1616        private SyncState(String jsonValue) {
1617            this.jsonValue = jsonValue;
1618        }
1619
1620        static SyncState fromJSONValue(String jsonValue) {
1621            return SyncState.valueOf(jsonValue.toUpperCase());
1622        }
1623
1624        String toJSONValue() {
1625            return this.jsonValue;
1626        }
1627    }
1628
1629    /**
1630     * Enumerates the possible permissions that a user can have on a folder.
1631     */
1632    public enum Permission {
1633        /**
1634         * The user can download the folder.
1635         */
1636        CAN_DOWNLOAD("can_download"),
1637
1638        /**
1639         * The user can upload to the folder.
1640         */
1641        CAN_UPLOAD("can_upload"),
1642
1643        /**
1644         * The user can rename the folder.
1645         */
1646        CAN_RENAME("can_rename"),
1647
1648        /**
1649         * The user can delete the folder.
1650         */
1651        CAN_DELETE("can_delete"),
1652
1653        /**
1654         * The user can share the folder.
1655         */
1656        CAN_SHARE("can_share"),
1657
1658        /**
1659         * The user can invite collaborators to the folder.
1660         */
1661        CAN_INVITE_COLLABORATOR("can_invite_collaborator"),
1662
1663        /**
1664         * The user can set the access level for shared links to the folder.
1665         */
1666        CAN_SET_SHARE_ACCESS("can_set_share_access");
1667
1668        private final String jsonValue;
1669
1670        private Permission(String jsonValue) {
1671            this.jsonValue = jsonValue;
1672        }
1673
1674        static Permission fromJSONValue(String jsonValue) {
1675            return Permission.valueOf(jsonValue.toUpperCase());
1676        }
1677
1678        String toJSONValue() {
1679            return this.jsonValue;
1680        }
1681    }
1682}