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