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