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() {
262        BoxAPIConnection api = this.getAPI();
263        URL url = GET_COLLABORATIONS_URL.build(api.getBaseURL(), this.getID());
264
265        BoxJSONRequest request = new BoxJSONRequest(api, url, "GET");
266        try (BoxJSONResponse response = request.send()) {
267            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
268
269            int entriesCount = responseJSON.get("total_count").asInt();
270            Collection<BoxCollaboration.Info> collaborations = new ArrayList<>(entriesCount);
271            JsonArray entries = responseJSON.get("entries").asArray();
272            for (JsonValue entry : entries) {
273                JsonObject entryObject = entry.asObject();
274                BoxCollaboration collaboration = new BoxCollaboration(api, entryObject.get("id").asString());
275                BoxCollaboration.Info info = collaboration.new Info(entryObject);
276                collaborations.add(info);
277            }
278
279            return collaborations;
280        }
281    }
282
283    @Override
284    public BoxFolder.Info getInfo(String... fields) {
285        URL url = FOLDER_INFO_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
286        if (fields.length > 0) {
287            String queryString = new QueryStringBuilder().appendParam("fields", fields).toString();
288            url = FOLDER_INFO_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
289        }
290
291        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET");
292        try (BoxJSONResponse response = request.send()) {
293            return new Info(response.getJSON());
294        }
295    }
296
297    /**
298     * Updates the information about this folder with any info fields that have been modified locally.
299     *
300     * @param info the updated info.
301     */
302    public void updateInfo(BoxFolder.Info info) {
303        URL url = FOLDER_INFO_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
304        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
305        request.setBody(info.getPendingChanges());
306        try (BoxJSONResponse response = request.send()) {
307            JsonObject jsonObject = Json.parse(response.getJSON()).asObject();
308            info.update(jsonObject);
309        }
310    }
311
312    @Override
313    public BoxFolder.Info copy(BoxFolder destination) {
314        return this.copy(destination, null);
315    }
316
317    @Override
318    public BoxFolder.Info copy(BoxFolder destination, String newName) {
319        URL url = COPY_FOLDER_URL.build(this.getAPI().getBaseURL(), this.getID());
320        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
321
322        JsonObject parent = new JsonObject();
323        parent.add("id", destination.getID());
324
325        JsonObject copyInfo = new JsonObject();
326        copyInfo.add("parent", parent);
327        if (newName != null) {
328            copyInfo.add("name", newName);
329        }
330
331        request.setBody(copyInfo.toString());
332        try (BoxJSONResponse response = request.send()) {
333            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
334            BoxFolder copiedFolder = new BoxFolder(this.getAPI(), responseJSON.get("id").asString());
335            return copiedFolder.new Info(responseJSON);
336        }
337    }
338
339    /**
340     * Creates a new child folder inside this folder.
341     *
342     * @param name the new folder's name.
343     * @return the created folder's info.
344     */
345    public BoxFolder.Info createFolder(String name) {
346        JsonObject parent = new JsonObject();
347        parent.add("id", this.getID());
348
349        JsonObject newFolder = new JsonObject();
350        newFolder.add("name", name);
351        newFolder.add("parent", parent);
352
353        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), CREATE_FOLDER_URL.build(this.getAPI().getBaseURL()),
354            "POST");
355        request.setBody(newFolder.toString());
356        try (BoxJSONResponse response = request.send()) {
357            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
358
359            BoxFolder createdFolder = new BoxFolder(this.getAPI(), responseJSON.get("id").asString());
360            return createdFolder.new Info(responseJSON);
361        }
362    }
363
364    /**
365     * Deletes this folder, optionally recursively deleting all of its contents.
366     *
367     * @param recursive true to recursively delete this folder's contents; otherwise false.
368     */
369    public void delete(boolean recursive) {
370        URL url = DELETE_FOLDER_URL.buildAlpha(this.getAPI().getBaseURL(), this.getID(), recursive);
371        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE");
372        request.send().close();
373    }
374
375    @Override
376    public BoxItem.Info move(BoxFolder destination) {
377        return this.move(destination, null);
378    }
379
380    @Override
381    public BoxItem.Info move(BoxFolder destination, String newName) {
382        URL url = FOLDER_INFO_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
383        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
384
385        JsonObject parent = new JsonObject();
386        parent.add("id", destination.getID());
387
388        JsonObject updateInfo = new JsonObject();
389        updateInfo.add("parent", parent);
390        if (newName != null) {
391            updateInfo.add("name", newName);
392        }
393
394        request.setBody(updateInfo.toString());
395        try (BoxJSONResponse response = request.send()) {
396            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
397            BoxFolder movedFolder = new BoxFolder(this.getAPI(), responseJSON.get("id").asString());
398            return movedFolder.new Info(responseJSON);
399        }
400    }
401
402    /**
403     * Renames this folder.
404     *
405     * @param newName the new name of the folder.
406     */
407    public void rename(String newName) {
408        URL url = FOLDER_INFO_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
409        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
410
411        JsonObject updateInfo = new JsonObject();
412        updateInfo.add("name", newName);
413
414        request.setBody(updateInfo.toString());
415        try (BoxJSONResponse response = request.send()) {
416            response.getJSON();
417        }
418    }
419
420    /**
421     * Checks if the file can be successfully uploaded by using the preflight check.
422     *
423     * @param name     the name to give the uploaded file.
424     * @param fileSize the size of the file used for account capacity calculations.
425     */
426    public void canUpload(String name, long fileSize) {
427        URL url = UPLOAD_FILE_URL.build(this.getAPI().getBaseURL());
428        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "OPTIONS");
429
430        JsonObject parent = new JsonObject();
431        parent.add("id", this.getID());
432
433        JsonObject preflightInfo = new JsonObject();
434        preflightInfo.add("parent", parent);
435        preflightInfo.add("name", name);
436
437        preflightInfo.add("size", fileSize);
438
439        request.setBody(preflightInfo.toString());
440        try (BoxJSONResponse response = request.send()) {
441            response.getJSON();
442        }
443    }
444
445    /**
446     * Uploads a new file to this folder.
447     *
448     * @param fileContent a stream containing the contents of the file to upload.
449     * @param name        the name to give the uploaded file.
450     * @return the uploaded file's info.
451     */
452    public BoxFile.Info uploadFile(InputStream fileContent, String name) {
453        FileUploadParams uploadInfo = new FileUploadParams()
454            .setContent(fileContent)
455            .setName(name);
456        return this.uploadFile(uploadInfo);
457    }
458
459    /**
460     * Uploads a new file to this folder.
461     *
462     * @param callback the callback which allows file content to be written on output stream.
463     * @param name     the name to give the uploaded file.
464     * @return the uploaded file's info.
465     */
466    public BoxFile.Info uploadFile(UploadFileCallback callback, String name) {
467        FileUploadParams uploadInfo = new FileUploadParams()
468            .setUploadFileCallback(callback)
469            .setName(name);
470        return this.uploadFile(uploadInfo);
471    }
472
473    /**
474     * Uploads a new file to this folder while reporting the progress to a ProgressListener.
475     *
476     * @param fileContent a stream containing the contents of the file to upload.
477     * @param name        the name to give the uploaded file.
478     * @param fileSize    the size of the file used for determining the progress of the upload.
479     * @param listener    a listener for monitoring the upload's progress.
480     * @return the uploaded file's info.
481     */
482    public BoxFile.Info uploadFile(InputStream fileContent, String name, long fileSize, ProgressListener listener) {
483        FileUploadParams uploadInfo = new FileUploadParams()
484            .setContent(fileContent)
485            .setName(name)
486            .setSize(fileSize)
487            .setProgressListener(listener);
488        return this.uploadFile(uploadInfo);
489    }
490
491    /**
492     * Uploads a new file to this folder with a specified file description.
493     *
494     * @param fileContent a stream containing the contents of the file to upload.
495     * @param name        the name to give the uploaded file.
496     * @param description the description to give the uploaded file.
497     * @return the uploaded file's info.
498     */
499    public BoxFile.Info uploadFile(InputStream fileContent, String name, String description) {
500        FileUploadParams uploadInfo = new FileUploadParams()
501            .setContent(fileContent)
502            .setName(name)
503            .setDescription(description);
504        return this.uploadFile(uploadInfo);
505    }
506
507    /**
508     * Uploads a new file to this folder with custom upload parameters.
509     *
510     * @param uploadParams the custom upload parameters.
511     * @return the uploaded file's info.
512     */
513    public BoxFile.Info uploadFile(FileUploadParams uploadParams) {
514        URL uploadURL = UPLOAD_FILE_URL.build(this.getAPI().getBaseUploadURL());
515        BoxMultipartRequest request = new BoxMultipartRequest(getAPI(), uploadURL);
516
517        JsonObject fieldJSON = new JsonObject();
518        JsonObject parentIdJSON = new JsonObject();
519        parentIdJSON.add("id", getID());
520        fieldJSON.add("name", uploadParams.getName());
521        fieldJSON.add("parent", parentIdJSON);
522
523        if (uploadParams.getCreated() != null) {
524            fieldJSON.add("content_created_at", BoxDateFormat.format(uploadParams.getCreated()));
525        }
526
527        if (uploadParams.getModified() != null) {
528            fieldJSON.add("content_modified_at", BoxDateFormat.format(uploadParams.getModified()));
529        }
530
531        if (uploadParams.getSHA1() != null && !uploadParams.getSHA1().isEmpty()) {
532            request.setContentSHA1(uploadParams.getSHA1());
533        }
534
535        if (uploadParams.getDescription() != null) {
536            fieldJSON.add("description", uploadParams.getDescription());
537        }
538
539        request.putField("attributes", fieldJSON.toString());
540
541        if (uploadParams.getSize() > 0) {
542            request.setFile(uploadParams.getContent(), uploadParams.getName(), uploadParams.getSize());
543        } else if (uploadParams.getContent() != null) {
544            request.setFile(uploadParams.getContent(), uploadParams.getName());
545        } else {
546            request.setUploadFileCallback(uploadParams.getUploadFileCallback(), uploadParams.getName());
547        }
548
549        BoxJSONResponse response = null;
550        try {
551            if (uploadParams.getProgressListener() == null) {
552                // upload files sends multipart request but response is JSON
553                response = (BoxJSONResponse) request.send();
554            } else {
555                // upload files sends multipart request but response is JSON
556                response = (BoxJSONResponse) request.send(uploadParams.getProgressListener());
557            }
558            JsonObject collection = Json.parse(response.getJSON()).asObject();
559            JsonArray entries = collection.get("entries").asArray();
560            JsonObject fileInfoJSON = entries.get(0).asObject();
561            String uploadedFileID = fileInfoJSON.get("id").asString();
562
563            BoxFile uploadedFile = new BoxFile(getAPI(), uploadedFileID);
564            return uploadedFile.new Info(fileInfoJSON);
565        } finally {
566            Optional.ofNullable(response).ifPresent(BoxAPIResponse::close);
567        }
568    }
569
570    /**
571     * Uploads a new weblink to this folder.
572     *
573     * @param linkURL the URL the weblink points to.
574     * @return the uploaded weblink's info.
575     */
576    public BoxWebLink.Info createWebLink(URL linkURL) {
577        return this.createWebLink(null, linkURL,
578            null);
579    }
580
581    /**
582     * Uploads a new weblink to this folder.
583     *
584     * @param name    the filename for the weblink.
585     * @param linkURL the URL the weblink points to.
586     * @return the uploaded weblink's info.
587     */
588    public BoxWebLink.Info createWebLink(String name, URL linkURL) {
589        return this.createWebLink(name, linkURL,
590            null);
591    }
592
593    /**
594     * Uploads a new weblink to this folder.
595     *
596     * @param linkURL     the URL the weblink points to.
597     * @param description the weblink's description.
598     * @return the uploaded weblink's info.
599     */
600    public BoxWebLink.Info createWebLink(URL linkURL, String description) {
601        return this.createWebLink(null, linkURL, description);
602    }
603
604    /**
605     * Uploads a new weblink to this folder.
606     *
607     * @param name        the filename for the weblink.
608     * @param linkURL     the URL the weblink points to.
609     * @param description the weblink's description.
610     * @return the uploaded weblink's info.
611     */
612    public BoxWebLink.Info createWebLink(String name, URL linkURL, String description) {
613        JsonObject parent = new JsonObject();
614        parent.add("id", this.getID());
615
616        JsonObject newWebLink = new JsonObject();
617        newWebLink.add("name", name);
618        newWebLink.add("parent", parent);
619        newWebLink.add("url", linkURL.toString());
620
621        if (description != null) {
622            newWebLink.add("description", description);
623        }
624
625        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(),
626            CREATE_WEB_LINK_URL.build(this.getAPI().getBaseURL()), "POST");
627        request.setBody(newWebLink.toString());
628        try (BoxJSONResponse response = request.send()) {
629            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
630
631            BoxWebLink createdWebLink = new BoxWebLink(this.getAPI(), responseJSON.get("id").asString());
632            return createdWebLink.new Info(responseJSON);
633        }
634    }
635
636    /**
637     * Returns an iterable containing the items in this folder. Iterating over the iterable returned by this method is
638     * equivalent to iterating over this BoxFolder directly.
639     *
640     * @return an iterable containing the items in this folder.
641     */
642    public Iterable<BoxItem.Info> getChildren() {
643        return this;
644    }
645
646    /**
647     * Returns an iterable containing the items in this folder and specifies which child fields to retrieve from the
648     * API.
649     *
650     * @param fields the fields to retrieve.
651     * @return an iterable containing the items in this folder.
652     */
653    public Iterable<BoxItem.Info> getChildren(final String... fields) {
654        return () -> {
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, marker(DEFAULT_LIMIT));
658        };
659    }
660
661    /**
662     * Returns an iterable containing the items in this folder sorted by name and direction.
663     *
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);
676        }
677        final String query = builder.toString();
678        return () -> {
679            URL url = GET_ITEMS_URL.buildWithQuery(getAPI().getBaseURL(), query, getID());
680            return new BoxItemIterator(getAPI(), url, offset(0, DEFAULT_LIMIT));
681        };
682    }
683
684    /**
685     * Returns an iterable containing the items in this folder sorted by name and direction.
686     *
687     * @param sort      the field to sort by, can be set as `name`, `id`, and `date`.
688     * @param direction the direction to display the item results.
689     * @param offset    the index of the first child item to retrieve.
690     * @param limit     the maximum number of children to retrieve after the offset.
691     * @param fields    the fields to retrieve.
692     * @return an iterable containing the items in this folder.
693     */
694    public Iterable<BoxItem.Info> getChildren(String sort, SortDirection direction, final long offset, final long limit,
695                                              final String... fields) {
696        QueryStringBuilder builder = new QueryStringBuilder()
697            .appendParam("sort", sort)
698            .appendParam("direction", direction.toString());
699
700        if (fields.length > 0) {
701            builder.appendParam("fields", fields);
702        }
703        final String query = builder.toString();
704        return () -> {
705            URL url = GET_ITEMS_URL.buildWithQuery(getAPI().getBaseURL(), query, getID());
706            return new BoxItemIterator(getAPI(), url, limit, offset);
707        };
708    }
709
710    /**
711     * Retrieves a specific range of child items in this folder.
712     *
713     * @param offset the index of the first child item to retrieve.
714     * @param limit  the maximum number of children to retrieve after the offset.
715     * @param fields the fields to retrieve.
716     * @return a partial collection containing the specified range of child items.
717     */
718    public PartialCollection<BoxItem.Info> getChildrenRange(long offset, long limit, String... fields) {
719        QueryStringBuilder builder = new QueryStringBuilder()
720            .appendParam("limit", limit)
721            .appendParam("offset", offset);
722
723        if (fields.length > 0) {
724            builder.appendParam("fields", fields);
725        }
726
727        URL url = GET_ITEMS_URL.buildWithQuery(getAPI().getBaseURL(), builder.toString(), getID());
728        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET");
729        try (BoxJSONResponse response = request.send()) {
730            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
731
732            String totalCountString = responseJSON.get("total_count").toString();
733            long fullSize = Double.valueOf(totalCountString).longValue();
734            PartialCollection<BoxItem.Info> children = new PartialCollection<>(offset, limit, fullSize);
735            JsonArray jsonArray = responseJSON.get("entries").asArray();
736            for (JsonValue value : jsonArray) {
737                JsonObject jsonObject = value.asObject();
738                BoxItem.Info parsedItemInfo = (BoxItem.Info) BoxResource.parseInfo(this.getAPI(), jsonObject);
739                if (parsedItemInfo != null) {
740                    children.add(parsedItemInfo);
741                }
742            }
743            return children;
744        }
745    }
746
747    /**
748     * Returns an iterable containing the items in this folder sorted by name and direction.
749     *
750     * @param sortParameters   describes sorting parameters.
751     *                         Sort parameters are supported only with offset based pagination.
752     *                         Use {@link SortParameters#none()} to ignore sorting.
753     * @param pagingParameters describes paging parameters.
754     * @param fields           the fields to retrieve.
755     * @return an iterable containing the items in this folder.
756     */
757    public Iterable<BoxItem.Info> getChildren(
758        final SortParameters sortParameters, final PagingParameters pagingParameters, String... fields
759    ) {
760        QueryStringBuilder builder = sortParameters.asQueryStringBuilder();
761        validateSortIsSelectedWithOffsetPaginationOnly(pagingParameters, builder);
762
763        if (fields.length > 0) {
764            builder.appendParam("fields", fields);
765        }
766        final String query = builder.toString();
767        return () -> {
768            URL url = GET_ITEMS_URL.buildWithQuery(getAPI().getBaseURL(), query, getID());
769            return new BoxItemIterator(getAPI(), url, pagingParameters);
770        };
771    }
772
773    /**
774     * Returns an iterator over the items in this folder.
775     *
776     * @return an iterator over the items in this folder.
777     */
778    @Override
779    public Iterator<BoxItem.Info> iterator() {
780        URL url = GET_ITEMS_URL.build(this.getAPI().getBaseURL(), BoxFolder.this.getID());
781        return new BoxItemIterator(BoxFolder.this.getAPI(), url, marker(DEFAULT_LIMIT));
782    }
783
784    /**
785     * Adds new {@link BoxWebHook} to this {@link BoxFolder}.
786     *
787     * @param address  {@link BoxWebHook.Info#getAddress()}
788     * @param triggers {@link BoxWebHook.Info#getTriggers()}
789     * @return created {@link BoxWebHook.Info}
790     */
791    public BoxWebHook.Info addWebHook(URL address, BoxWebHook.Trigger... triggers) {
792        return BoxWebHook.create(this, address, triggers);
793    }
794
795    /**
796     * Used to retrieve the watermark for the folder.
797     * If the folder does not have a watermark applied to it, a 404 Not Found will be returned by API.
798     *
799     * @param fields the fields to retrieve.
800     * @return the watermark associated with the folder.
801     */
802    public BoxWatermark getWatermark(String... fields) {
803        return this.getWatermark(FOLDER_INFO_URL_TEMPLATE, fields);
804    }
805
806    /**
807     * Used to apply or update the watermark for the folder.
808     *
809     * @return the watermark associated with the folder.
810     */
811    public BoxWatermark applyWatermark() {
812        return this.applyWatermark(FOLDER_INFO_URL_TEMPLATE, BoxWatermark.WATERMARK_DEFAULT_IMPRINT);
813    }
814
815    /**
816     * Removes a watermark from the folder.
817     * If the folder did not have a watermark applied to it, a 404 Not Found will be returned by API.
818     */
819    public void removeWatermark() {
820        this.removeWatermark(FOLDER_INFO_URL_TEMPLATE);
821    }
822
823    /**
824     * Used to retrieve all metadata associated with the folder.
825     *
826     * @param fields the optional fields to retrieve.
827     * @return An iterable of metadata instances associated with the folder
828     */
829    public Iterable<Metadata> getAllMetadata(String... fields) {
830        return Metadata.getAllMetadata(this, fields);
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        try (BoxJSONResponse response = request.send()) {
849            JsonObject jsonObject = Json.parse(response.getJSON()).asObject();
850            return new Info(jsonObject);
851        }
852    }
853
854    /**
855     * Creates global property metadata on this folder.
856     *
857     * @param metadata the new metadata values.
858     * @return the metadata returned from the server.
859     */
860    public Metadata createMetadata(Metadata metadata) {
861        return this.createMetadata(Metadata.DEFAULT_METADATA_TYPE, metadata);
862    }
863
864    /**
865     * Creates metadata on this folder using a specified template.
866     *
867     * @param templateName the name of the metadata template.
868     * @param metadata     the new metadata values.
869     * @return the metadata returned from the server.
870     */
871    public Metadata createMetadata(String templateName, Metadata metadata) {
872        String scope = Metadata.scopeBasedOnType(templateName);
873        return this.createMetadata(templateName, scope, metadata);
874    }
875
876    /**
877     * Creates metadata on this folder using a specified scope and template.
878     *
879     * @param templateName the name of the metadata template.
880     * @param scope        the scope of the template (usually "global" or "enterprise").
881     * @param metadata     the new metadata values.
882     * @return the metadata returned from the server.
883     */
884    public Metadata createMetadata(String templateName, String scope, Metadata metadata) {
885        URL url = METADATA_URL_TEMPLATE.buildAlpha(this.getAPI().getBaseURL(), this.getID(), scope, templateName);
886        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
887        request.setBody(metadata.toString());
888        try (BoxJSONResponse response = request.send()) {
889            return new Metadata(Json.parse(response.getJSON()).asObject());
890        }
891    }
892
893    /**
894     * Sets the provided metadata on the folder. If metadata has already been created on this folder,
895     * it overwrites metadata keys specified in the `metadata` param.
896     *
897     * @param templateName the name of the metadata template.
898     * @param scope        the scope of the template (usually "global" or "enterprise").
899     * @param metadata     the new metadata values.
900     * @return the metadata returned from the server.
901     */
902    public Metadata setMetadata(String templateName, String scope, Metadata metadata) {
903        try {
904            return this.createMetadata(templateName, scope, metadata);
905        } catch (BoxAPIException e) {
906            if (e.getResponseCode() == 409) {
907                if (metadata.getOperations().isEmpty()) {
908                    return getMetadata();
909                } else {
910                    return updateExistingTemplate(templateName, scope, metadata);
911                }
912            } else {
913                throw e;
914            }
915        }
916    }
917
918    /**
919     * Throws IllegalArgumentException exception when sorting and marker pagination is selected.
920     *
921     * @param pagingParameters paging definition to check
922     * @param sortQuery        builder containing sort query
923     */
924    private void validateSortIsSelectedWithOffsetPaginationOnly(
925        PagingParameters pagingParameters,
926        QueryStringBuilder sortQuery
927    ) {
928        if (pagingParameters != null && pagingParameters.isMarkerBasedPaging() && sortQuery.toString().length() > 0) {
929            throw new IllegalArgumentException("Sorting is not supported when using marker based pagination.");
930        }
931    }
932
933    private Metadata updateExistingTemplate(String templateName, String scope, Metadata metadata) {
934        Metadata metadataToUpdate = new Metadata(scope, templateName);
935        for (JsonValue value : metadata.getOperations()) {
936            if (value.asObject().get("value").isNumber()) {
937                metadataToUpdate.add(value.asObject().get("path").asString(),
938                    value.asObject().get("value").asDouble());
939            } else if (value.asObject().get("value").isString()) {
940                metadataToUpdate.add(value.asObject().get("path").asString(),
941                    value.asObject().get("value").asString());
942            } else if (value.asObject().get("value").isArray()) {
943                ArrayList<String> list = new ArrayList<>();
944                for (JsonValue jsonValue : value.asObject().get("value").asArray()) {
945                    list.add(jsonValue.asString());
946                }
947                metadataToUpdate.add(value.asObject().get("path").asString(), list);
948            }
949        }
950        return this.updateMetadata(metadataToUpdate);
951    }
952
953    /**
954     * Gets the global properties metadata on this folder.
955     *
956     * @return the metadata returned from the server.
957     */
958    public Metadata getMetadata() {
959        return this.getMetadata(Metadata.DEFAULT_METADATA_TYPE);
960    }
961
962    /**
963     * Gets the metadata on this folder associated with a specified template.
964     *
965     * @param templateName the metadata template type name.
966     * @return the metadata returned from the server.
967     */
968    public Metadata getMetadata(String templateName) {
969        String scope = Metadata.scopeBasedOnType(templateName);
970        return this.getMetadata(templateName, scope);
971    }
972
973    /**
974     * Gets the metadata on this folder associated with a specified scope and template.
975     *
976     * @param templateName the metadata template type name.
977     * @param scope        the scope of the template (usually "global" or "enterprise").
978     * @return the metadata returned from the server.
979     */
980    public Metadata getMetadata(String templateName, String scope) {
981        URL url = METADATA_URL_TEMPLATE.buildAlpha(this.getAPI().getBaseURL(), this.getID(), scope, templateName);
982        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET");
983        try (BoxJSONResponse response = request.send()) {
984            return new Metadata(Json.parse(response.getJSON()).asObject());
985        }
986    }
987
988    /**
989     * Updates the folder metadata.
990     *
991     * @param metadata the new metadata values.
992     * @return the metadata returned from the server.
993     */
994    public Metadata updateMetadata(Metadata metadata) {
995        URL url = METADATA_URL_TEMPLATE.buildAlpha(this.getAPI().getBaseURL(), this.getID(), metadata.getScope(),
996            metadata.getTemplateName());
997        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT", APPLICATION_JSON_PATCH);
998        request.setBody(metadata.getPatch());
999        try (BoxJSONResponse response = request.send()) {
1000            return new Metadata(Json.parse(response.getJSON()).asObject());
1001        }
1002    }
1003
1004    /**
1005     * Deletes the global properties metadata on this folder.
1006     */
1007    public void deleteMetadata() {
1008        this.deleteMetadata(Metadata.DEFAULT_METADATA_TYPE);
1009    }
1010
1011    /**
1012     * Deletes the metadata on this folder associated with a specified template.
1013     *
1014     * @param templateName the metadata template type name.
1015     */
1016    public void deleteMetadata(String templateName) {
1017        String scope = Metadata.scopeBasedOnType(templateName);
1018        this.deleteMetadata(templateName, scope);
1019    }
1020
1021    /**
1022     * Deletes the metadata on this folder associated with a specified scope and template.
1023     *
1024     * @param templateName the metadata template type name.
1025     * @param scope        the scope of the template (usually "global" or "enterprise").
1026     */
1027    public void deleteMetadata(String templateName, String scope) {
1028        URL url = METADATA_URL_TEMPLATE.buildAlpha(this.getAPI().getBaseURL(), this.getID(), scope, templateName);
1029        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE");
1030        request.send().close();
1031    }
1032
1033    /**
1034     * Adds a metadata classification to the specified file.
1035     *
1036     * @param classificationType the metadata classification type.
1037     * @return the metadata classification type added to the file.
1038     */
1039    public String addClassification(String classificationType) {
1040        Metadata metadata = new Metadata().add(Metadata.CLASSIFICATION_KEY, classificationType);
1041        Metadata classification = this.createMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY,
1042            "enterprise", metadata);
1043
1044        return classification.getString(Metadata.CLASSIFICATION_KEY);
1045    }
1046
1047    /**
1048     * Updates a metadata classification on the specified file.
1049     *
1050     * @param classificationType the metadata classification type.
1051     * @return the new metadata classification type updated on the file.
1052     */
1053    public String updateClassification(String classificationType) {
1054        Metadata metadata = new Metadata("enterprise", Metadata.CLASSIFICATION_TEMPLATE_KEY);
1055        metadata.replace(Metadata.CLASSIFICATION_KEY, classificationType);
1056        Metadata classification = this.updateMetadata(metadata);
1057
1058        return classification.getString(Metadata.CLASSIFICATION_KEY);
1059    }
1060
1061    /**
1062     * Attempts to add classification to a file. If classification already exists then do update.
1063     *
1064     * @param classificationType the metadata classification type.
1065     * @return the metadata classification type on the file.
1066     */
1067    public String setClassification(String classificationType) {
1068        Metadata metadata = new Metadata().add(Metadata.CLASSIFICATION_KEY, classificationType);
1069        Metadata classification;
1070
1071        try {
1072            classification = this.createMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY, "enterprise", metadata);
1073        } catch (BoxAPIException e) {
1074            if (e.getResponseCode() == 409) {
1075                metadata = new Metadata("enterprise", Metadata.CLASSIFICATION_TEMPLATE_KEY);
1076                metadata.replace(Metadata.CLASSIFICATION_KEY, classificationType);
1077                classification = this.updateMetadata(metadata);
1078            } else {
1079                throw e;
1080            }
1081        }
1082
1083        return classification.getString("/Box__Security__Classification__Key");
1084    }
1085
1086    /**
1087     * Gets the classification type for the specified file.
1088     *
1089     * @return the metadata classification type on the file.
1090     */
1091    public String getClassification() {
1092        Metadata metadata = this.getMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY);
1093        return metadata.getString(Metadata.CLASSIFICATION_KEY);
1094    }
1095
1096    /**
1097     * Deletes the classification on the file.
1098     */
1099    public void deleteClassification() {
1100        this.deleteMetadata(Metadata.CLASSIFICATION_TEMPLATE_KEY, "enterprise");
1101    }
1102
1103    /**
1104     * Creates an upload session to create a new file in chunks.
1105     * This will first verify that the file can be created and then open a session for uploading pieces of the file.
1106     *
1107     * @param fileName the name of the file to be created
1108     * @param fileSize the size of the file that will be uploaded
1109     * @return the created upload session instance
1110     */
1111    public BoxFileUploadSession.Info createUploadSession(String fileName, long fileSize) {
1112
1113        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL());
1114        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
1115
1116        JsonObject body = new JsonObject();
1117        body.add("folder_id", this.getID());
1118        body.add("file_name", fileName);
1119        body.add("file_size", fileSize);
1120        request.setBody(body.toString());
1121
1122        try (BoxJSONResponse response = request.send()) {
1123            JsonObject jsonObject = Json.parse(response.getJSON()).asObject();
1124
1125            String sessionId = jsonObject.get("id").asString();
1126            BoxFileUploadSession session = new BoxFileUploadSession(this.getAPI(), sessionId);
1127
1128            return session.new Info(jsonObject);
1129        }
1130    }
1131
1132    /**
1133     * Creates a new file.
1134     *
1135     * @param inputStream the stream instance that contains the data.
1136     * @param fileName    the name of the file to be created.
1137     * @param fileSize    the size of the file that will be uploaded.
1138     * @return the created file instance.
1139     * @throws InterruptedException when a thread execution is interrupted.
1140     * @throws IOException          when reading a stream throws exception.
1141     */
1142    public BoxFile.Info uploadLargeFile(InputStream inputStream, String fileName, long fileSize)
1143        throws InterruptedException, IOException {
1144        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL());
1145        this.canUpload(fileName, fileSize);
1146        return new LargeFileUpload().
1147            upload(this.getAPI(), this.getID(), inputStream, url, fileName, fileSize);
1148    }
1149
1150    /**
1151     * Creates a new file.  Also sets file attributes.
1152     *
1153     * @param inputStream    the stream instance that contains the data.
1154     * @param fileName       the name of the file to be created.
1155     * @param fileSize       the size of the file that will be uploaded.
1156     * @param fileAttributes file attributes to set
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                                        Map<String, String> fileAttributes)
1163        throws InterruptedException, IOException {
1164        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL());
1165        this.canUpload(fileName, fileSize);
1166        return new LargeFileUpload().
1167            upload(this.getAPI(), this.getID(), inputStream, url, fileName, fileSize, fileAttributes);
1168    }
1169
1170    /**
1171     * Creates a new file using specified number of parallel http connections.
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     * @return the created file instance.
1180     * @throws InterruptedException when a thread execution is interrupted.
1181     * @throws IOException          when reading a stream throws exception.
1182     */
1183    public BoxFile.Info uploadLargeFile(InputStream inputStream, String fileName, long fileSize,
1184                                        int nParallelConnections, long timeOut, TimeUnit unit)
1185        throws InterruptedException, IOException {
1186        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL());
1187        this.canUpload(fileName, fileSize);
1188        return new LargeFileUpload(nParallelConnections, timeOut, unit).
1189            upload(this.getAPI(), this.getID(), inputStream, url, fileName, fileSize);
1190    }
1191
1192    /**
1193     * Creates a new file using specified number of parallel http connections.  Also sets file attributes.
1194     *
1195     * @param inputStream          the stream instance that contains the data.
1196     * @param fileName             the name of the file to be created.
1197     * @param fileSize             the size of the file that will be uploaded.
1198     * @param nParallelConnections number of parallel http connections to use
1199     * @param timeOut              time to wait before killing the job
1200     * @param unit                 time unit for the time wait value
1201     * @param fileAttributes       file attributes to set
1202     * @return the created file instance.
1203     * @throws InterruptedException when a thread execution is interrupted.
1204     * @throws IOException          when reading a stream throws exception.
1205     */
1206    public BoxFile.Info uploadLargeFile(InputStream inputStream, String fileName, long fileSize,
1207                                        int nParallelConnections, long timeOut, TimeUnit unit,
1208                                        Map<String, String> fileAttributes)
1209        throws InterruptedException, IOException {
1210        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL());
1211        this.canUpload(fileName, fileSize);
1212        return new LargeFileUpload(nParallelConnections, timeOut, unit).
1213            upload(this.getAPI(), this.getID(), inputStream, url, fileName, fileSize, fileAttributes);
1214    }
1215
1216    /**
1217     * Creates a new Metadata Cascade Policy on a folder.
1218     *
1219     * @param scope       the scope of the metadata cascade policy.
1220     * @param templateKey the key of the template.
1221     * @return information about the Metadata Cascade Policy.
1222     */
1223    public BoxMetadataCascadePolicy.Info addMetadataCascadePolicy(String scope, String templateKey) {
1224
1225        return BoxMetadataCascadePolicy.create(this.getAPI(), this.getID(), scope, templateKey);
1226    }
1227
1228    /**
1229     * Retrieves all Metadata Cascade Policies on a folder.
1230     *
1231     * @param fields optional fields to retrieve for cascade policies.
1232     * @return the Iterable of Box Metadata Cascade Policies in your enterprise.
1233     */
1234    public Iterable<BoxMetadataCascadePolicy.Info> getMetadataCascadePolicies(String... fields) {
1235        return BoxMetadataCascadePolicy.getAll(this.getAPI(), this.getID(), fields);
1236    }
1237
1238    /**
1239     * Retrieves all Metadata Cascade Policies on a folder.
1240     *
1241     * @param enterpriseID the ID of the enterprise to retrieve cascade policies for.
1242     * @param limit        the number of entries of cascade policies to retrieve.
1243     * @param fields       optional fields to retrieve for cascade policies.
1244     * @return the Iterable of Box Metadata Cascade Policies in your enterprise.
1245     */
1246    public Iterable<BoxMetadataCascadePolicy.Info> getMetadataCascadePolicies(String enterpriseID,
1247                                                                              int limit, String... fields) {
1248
1249        return BoxMetadataCascadePolicy.getAll(this.getAPI(), this.getID(), enterpriseID, limit, fields);
1250    }
1251
1252    /**
1253     * Lock this folder.
1254     *
1255     * @return a created folder lock object.
1256     */
1257    public BoxFolderLock.Info lock() {
1258        JsonObject folderObject = new JsonObject();
1259        folderObject.add("type", "folder");
1260        folderObject.add("id", this.getID());
1261
1262        JsonObject lockedOperations = new JsonObject();
1263        lockedOperations.add("move", true);
1264        lockedOperations.add("delete", true);
1265
1266
1267        JsonObject body = new JsonObject();
1268        body.add("folder", folderObject);
1269        body.add("locked_operations", lockedOperations);
1270
1271        BoxJSONRequest request =
1272            new BoxJSONRequest(this.getAPI(), FOLDER_LOCK_URL_TEMPLATE.build(this.getAPI().getBaseURL()),
1273                "POST");
1274        request.setBody(body.toString());
1275        try (BoxJSONResponse response = request.send()) {
1276            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
1277
1278            BoxFolderLock createdFolderLock = new BoxFolderLock(this.getAPI(), responseJSON.get("id").asString());
1279            return createdFolderLock.new Info(responseJSON);
1280        }
1281    }
1282
1283    /**
1284     * Get the lock on this folder.
1285     *
1286     * @return a folder lock object.
1287     */
1288    public Iterable<BoxFolderLock.Info> getLocks() {
1289        String queryString = new QueryStringBuilder().appendParam("folder_id", this.getID()).toString();
1290        final BoxAPIConnection api = this.getAPI();
1291        return new BoxResourceIterable<BoxFolderLock.Info>(api,
1292            FOLDER_LOCK_URL_TEMPLATE.buildWithQuery(api.getBaseURL(), queryString), 100) {
1293            @Override
1294            protected BoxFolderLock.Info factory(JsonObject jsonObject) {
1295                BoxFolderLock folderLock =
1296                    new BoxFolderLock(api, jsonObject.get("id").asString());
1297                return folderLock.new Info(jsonObject);
1298            }
1299        };
1300    }
1301
1302    /**
1303     * Used to specify what direction to sort and display results.
1304     */
1305    public enum SortDirection {
1306        /**
1307         * ASC for ascending order.
1308         */
1309        ASC,
1310
1311        /**
1312         * DESC for descending order.
1313         */
1314        DESC
1315    }
1316
1317    /**
1318     * Enumerates the possible sync states that a folder can have.
1319     */
1320    public enum SyncState {
1321        /**
1322         * The folder is synced.
1323         */
1324        SYNCED("synced"),
1325
1326        /**
1327         * The folder is not synced.
1328         */
1329        NOT_SYNCED("not_synced"),
1330
1331        /**
1332         * The folder is partially synced.
1333         */
1334        PARTIALLY_SYNCED("partially_synced");
1335
1336        private final String jsonValue;
1337
1338        SyncState(String jsonValue) {
1339            this.jsonValue = jsonValue;
1340        }
1341
1342        static SyncState fromJSONValue(String jsonValue) {
1343            return SyncState.valueOf(jsonValue.toUpperCase());
1344        }
1345
1346        String toJSONValue() {
1347            return this.jsonValue;
1348        }
1349    }
1350
1351    /**
1352     * Enumerates the possible permissions that a user can have on a folder.
1353     */
1354    public enum Permission {
1355        /**
1356         * The user can download the folder.
1357         */
1358        CAN_DOWNLOAD("can_download"),
1359
1360        /**
1361         * The user can upload to the folder.
1362         */
1363        CAN_UPLOAD("can_upload"),
1364
1365        /**
1366         * The user can rename the folder.
1367         */
1368        CAN_RENAME("can_rename"),
1369
1370        /**
1371         * The user can delete the folder.
1372         */
1373        CAN_DELETE("can_delete"),
1374
1375        /**
1376         * The user can share the folder.
1377         */
1378        CAN_SHARE("can_share"),
1379
1380        /**
1381         * The user can invite collaborators to the folder.
1382         */
1383        CAN_INVITE_COLLABORATOR("can_invite_collaborator"),
1384
1385        /**
1386         * The user can set the access level for shared links to the folder.
1387         */
1388        CAN_SET_SHARE_ACCESS("can_set_share_access");
1389
1390        private final String jsonValue;
1391
1392        Permission(String jsonValue) {
1393            this.jsonValue = jsonValue;
1394        }
1395
1396        static Permission fromJSONValue(String jsonValue) {
1397            return Permission.valueOf(jsonValue.toUpperCase());
1398        }
1399
1400        String toJSONValue() {
1401            return this.jsonValue;
1402        }
1403    }
1404
1405    /**
1406     * Contains information about a BoxFolder.
1407     */
1408    public class Info extends BoxItem.Info {
1409        private BoxUploadEmail uploadEmail;
1410        private boolean hasCollaborations;
1411        private SyncState syncState;
1412        private EnumSet<Permission> permissions;
1413        private boolean canNonOwnersInvite;
1414        private boolean isWatermarked;
1415        private boolean isCollaborationRestrictedToEnterprise;
1416        private boolean isExternallyOwned;
1417        private Map<String, Map<String, Metadata>> metadataMap;
1418        private List<String> allowedSharedLinkAccessLevels;
1419        private List<String> allowedInviteeRoles;
1420        private BoxClassification classification;
1421
1422        private boolean isAccessibleViaSharedLink;
1423
1424        /**
1425         * Constructs an empty Info object.
1426         */
1427        public Info() {
1428            super();
1429        }
1430
1431        /**
1432         * Constructs an Info object by parsing information from a JSON string.
1433         *
1434         * @param json the JSON string to parse.
1435         */
1436        public Info(String json) {
1437            super(json);
1438        }
1439
1440        /**
1441         * Constructs an Info object using an already parsed JSON object.
1442         *
1443         * @param jsonObject the parsed JSON object.
1444         */
1445        public Info(JsonObject jsonObject) {
1446            super(jsonObject);
1447        }
1448
1449        /**
1450         * Gets the upload email for the folder.
1451         *
1452         * @return the upload email for the folder.
1453         */
1454        public BoxUploadEmail getUploadEmail() {
1455            return this.uploadEmail;
1456        }
1457
1458        /**
1459         * Sets the upload email for the folder.
1460         *
1461         * @param uploadEmail the upload email for the folder.
1462         */
1463        public void setUploadEmail(BoxUploadEmail uploadEmail) {
1464            if (this.uploadEmail == uploadEmail) {
1465                return;
1466            }
1467
1468            this.removeChildObject("folder_upload_email");
1469            this.uploadEmail = uploadEmail;
1470
1471            if (uploadEmail == null) {
1472                this.addPendingChange("folder_upload_email", (String) null);
1473            } else {
1474                this.addChildObject("folder_upload_email", uploadEmail);
1475            }
1476        }
1477
1478        /**
1479         * Gets whether or not the folder has any collaborations.
1480         *
1481         * @return true if the folder has collaborations; otherwise false.
1482         */
1483        public boolean getHasCollaborations() {
1484            return this.hasCollaborations;
1485        }
1486
1487        /**
1488         * Gets the sync state of the folder.
1489         *
1490         * @return the sync state of the folder.
1491         */
1492        public SyncState getSyncState() {
1493            return this.syncState;
1494        }
1495
1496        /**
1497         * Sets the sync state of the folder.
1498         *
1499         * @param syncState the sync state of the folder.
1500         */
1501        public void setSyncState(SyncState syncState) {
1502            this.syncState = syncState;
1503            this.addPendingChange("sync_state", syncState.toJSONValue());
1504        }
1505
1506        /**
1507         * Gets the permissions that the current user has on the folder.
1508         *
1509         * @return the permissions that the current user has on the folder.
1510         */
1511        public EnumSet<Permission> getPermissions() {
1512            return this.permissions;
1513        }
1514
1515        /**
1516         * Gets whether or not the non-owners can invite collaborators to the folder.
1517         *
1518         * @return [description]
1519         */
1520        public boolean getCanNonOwnersInvite() {
1521            return this.canNonOwnersInvite;
1522        }
1523
1524        /**
1525         * Sets whether or not non-owners can invite collaborators to the folder.
1526         *
1527         * @param canNonOwnersInvite indicates non-owners can invite collaborators to the folder.
1528         */
1529        public void setCanNonOwnersInvite(boolean canNonOwnersInvite) {
1530            this.canNonOwnersInvite = canNonOwnersInvite;
1531            this.addPendingChange("can_non_owners_invite", canNonOwnersInvite);
1532        }
1533
1534        /**
1535         * Gets whether future collaborations should be restricted to within the enterprise only.
1536         *
1537         * @return indicates whether collaboration is restricted to enterprise only.
1538         */
1539        public boolean getIsCollaborationRestrictedToEnterprise() {
1540            return this.isCollaborationRestrictedToEnterprise;
1541        }
1542
1543        /**
1544         * Sets whether future collaborations should be restricted to within the enterprise only.
1545         *
1546         * @param isRestricted indicates whether there is collaboration restriction within enterprise.
1547         */
1548        public void setIsCollaborationRestrictedToEnterprise(boolean isRestricted) {
1549            this.isCollaborationRestrictedToEnterprise = isRestricted;
1550            this.addPendingChange("is_collaboration_restricted_to_enterprise", isRestricted);
1551        }
1552
1553        /**
1554         * Retrieves the allowed roles for collaborations.
1555         *
1556         * @return the roles allowed for collaboration.
1557         */
1558        public List<String> getAllowedInviteeRoles() {
1559            return this.allowedInviteeRoles;
1560        }
1561
1562        /**
1563         * Retrieves the allowed access levels for a shared link.
1564         *
1565         * @return the allowed access levels for a shared link.
1566         */
1567        public List<String> getAllowedSharedLinkAccessLevels() {
1568            return this.allowedSharedLinkAccessLevels;
1569        }
1570
1571        /**
1572         * Gets flag indicating whether this file is Watermarked.
1573         *
1574         * @return whether the file is watermarked or not
1575         */
1576        public boolean getIsWatermarked() {
1577            return this.isWatermarked;
1578        }
1579
1580        /**
1581         * Gets the metadata on this folder associated with a specified scope and template.
1582         * Makes an attempt to get metadata that was retrieved using getInfo(String ...) method.
1583         *
1584         * @param templateName the metadata template type name.
1585         * @param scope        the scope of the template (usually "global" or "enterprise").
1586         * @return the metadata returned from the server.
1587         */
1588        public Metadata getMetadata(String templateName, String scope) {
1589            try {
1590                return this.metadataMap.get(scope).get(templateName);
1591            } catch (NullPointerException e) {
1592                return null;
1593            }
1594        }
1595
1596        /**
1597         * Get the field is_externally_owned determining whether this folder is owned by a user outside of the
1598         * enterprise.
1599         *
1600         * @return a boolean indicating whether this folder is owned by a user outside the enterprise.
1601         */
1602        public boolean getIsExternallyOwned() {
1603            return this.isExternallyOwned;
1604        }
1605
1606        /**
1607         * Gets the metadata classification type of this folder.
1608         *
1609         * @return the metadata classification type of this folder.
1610         */
1611        public BoxClassification getClassification() {
1612            return this.classification;
1613        }
1614
1615        /**
1616         * Returns the flag indicating whether the folder is accessible via a shared link.
1617         *
1618         * @return boolean flag indicating whether the folder is accessible via a shared link.
1619         */
1620        public boolean getIsAccessibleViaSharedLink() {
1621            return this.isAccessibleViaSharedLink;
1622        }
1623
1624
1625        @Override
1626        public BoxFolder getResource() {
1627            return BoxFolder.this;
1628        }
1629
1630        @Override
1631        protected void parseJSONMember(JsonObject.Member member) {
1632            super.parseJSONMember(member);
1633
1634            String memberName = member.getName();
1635            JsonValue value = member.getValue();
1636            try {
1637                switch (memberName) {
1638                    case "folder_upload_email":
1639                        if (this.uploadEmail == null) {
1640                            this.uploadEmail = new BoxUploadEmail(value.asObject());
1641                        } else {
1642                            this.uploadEmail.update(value.asObject());
1643                        }
1644                        break;
1645                    case "has_collaborations":
1646                        this.hasCollaborations = value.asBoolean();
1647                        break;
1648                    case "sync_state":
1649                        this.syncState = SyncState.fromJSONValue(value.asString());
1650                        break;
1651                    case "permissions":
1652                        this.permissions = this.parsePermissions(value.asObject());
1653                        break;
1654                    case "can_non_owners_invite":
1655                        this.canNonOwnersInvite = value.asBoolean();
1656                        break;
1657                    case "allowed_shared_link_access_levels":
1658                        this.allowedSharedLinkAccessLevels = this.parseSharedLinkAccessLevels(value.asArray());
1659                        break;
1660                    case "allowed_invitee_roles":
1661                        this.allowedInviteeRoles = this.parseAllowedInviteeRoles(value.asArray());
1662                        break;
1663                    case "is_collaboration_restricted_to_enterprise":
1664                        this.isCollaborationRestrictedToEnterprise = value.asBoolean();
1665                        break;
1666                    case "is_externally_owned":
1667                        this.isExternallyOwned = value.asBoolean();
1668                        break;
1669                    case "watermark_info":
1670                        this.isWatermarked = value.asObject().get("is_watermarked").asBoolean();
1671                        break;
1672                    case "metadata":
1673                        this.metadataMap = Parsers.parseAndPopulateMetadataMap(value.asObject());
1674                        break;
1675                    case "classification":
1676                        if (value.isNull()) {
1677                            this.classification = null;
1678                        } else {
1679                            this.classification = new BoxClassification(value.asObject());
1680                        }
1681                        break;
1682                    case "is_accessible_via_shared_link":
1683                        this.isAccessibleViaSharedLink = value.asBoolean();
1684                        break;
1685                    default:
1686                        break;
1687                }
1688            } catch (Exception e) {
1689                throw new BoxDeserializationException(memberName, value.toString(), e);
1690            }
1691        }
1692
1693        private EnumSet<Permission> parsePermissions(JsonObject jsonObject) {
1694            EnumSet<Permission> permissions = EnumSet.noneOf(Permission.class);
1695            for (JsonObject.Member member : jsonObject) {
1696                JsonValue value = member.getValue();
1697                if (value.isNull() || !value.asBoolean()) {
1698                    continue;
1699                }
1700
1701                switch (member.getName()) {
1702                    case "can_download":
1703                        permissions.add(Permission.CAN_DOWNLOAD);
1704                        break;
1705                    case "can_upload":
1706                        permissions.add(Permission.CAN_UPLOAD);
1707                        break;
1708                    case "can_rename":
1709                        permissions.add(Permission.CAN_RENAME);
1710                        break;
1711                    case "can_delete":
1712                        permissions.add(Permission.CAN_DELETE);
1713                        break;
1714                    case "can_share":
1715                        permissions.add(Permission.CAN_SHARE);
1716                        break;
1717                    case "can_invite_collaborator":
1718                        permissions.add(Permission.CAN_INVITE_COLLABORATOR);
1719                        break;
1720                    case "can_set_share_access":
1721                        permissions.add(Permission.CAN_SET_SHARE_ACCESS);
1722                        break;
1723                    default:
1724                        break;
1725                }
1726            }
1727
1728            return permissions;
1729        }
1730
1731        private List<String> parseSharedLinkAccessLevels(JsonArray jsonArray) {
1732            List<String> accessLevels = new ArrayList<>(jsonArray.size());
1733            for (JsonValue value : jsonArray) {
1734                accessLevels.add(value.asString());
1735            }
1736
1737            return accessLevels;
1738        }
1739
1740        private List<String> parseAllowedInviteeRoles(JsonArray jsonArray) {
1741            List<String> roles = new ArrayList<>(jsonArray.size());
1742            for (JsonValue value : jsonArray) {
1743                roles.add(value.asString());
1744            }
1745
1746            return roles;
1747        }
1748    }
1749}