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