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