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