001package com.box.sdk;
002
003import java.io.IOException;
004import java.io.InputStream;
005import java.net.URL;
006import java.util.ArrayList;
007import java.util.Collection;
008import java.util.Date;
009import java.util.EnumSet;
010import java.util.Iterator;
011import java.util.Map;
012import java.util.concurrent.TimeUnit;
013
014import com.box.sdk.internal.utils.Parsers;
015import com.eclipsesource.json.JsonArray;
016import com.eclipsesource.json.JsonObject;
017import com.eclipsesource.json.JsonValue;
018
019/**
020 * Represents a folder on Box. This class can be used to iterate through a folder's contents, collaborate a folder with
021 * another user or group, and perform other common folder operations (move, copy, delete, etc.).
022 * <p>
023 * <p>Unless otherwise noted, the methods in this class can throw an unchecked {@link BoxAPIException} (unchecked
024 * meaning that the compiler won't force you to handle it) if an error occurs. If you wish to implement custom error
025 * handling for errors related to the Box REST API, you should capture this exception explicitly.</p>
026 */
027@BoxResourceType("folder")
028public class BoxFolder extends BoxItem implements Iterable<BoxItem.Info> {
029    /**
030     * An array of all possible folder fields that can be requested when calling {@link #getInfo()}.
031     */
032    public static final String[] ALL_FIELDS = {"type", "id", "sequence_id", "etag", "name", "created_at", "modified_at",
033        "description", "size", "path_collection", "created_by", "modified_by", "trashed_at", "purged_at",
034        "content_created_at", "content_modified_at", "owned_by", "shared_link", "folder_upload_email", "parent",
035        "item_status", "item_collection", "sync_state", "has_collaborations", "permissions", "tags",
036        "can_non_owners_invite", "collections", "watermark_info", "metadata"};
037
038    /**
039     * Create Folder URL Template.
040     */
041    public static final URLTemplate CREATE_FOLDER_URL = new URLTemplate("folders");
042    /**
043     * Create Web Link URL Template.
044     */
045    public static final URLTemplate CREATE_WEB_LINK_URL = new URLTemplate("web_links");
046    /**
047     * Copy Folder URL Template.
048     */
049    public static final URLTemplate COPY_FOLDER_URL = new URLTemplate("folders/%s/copy");
050    /**
051     * Delete Folder URL Template.
052     */
053    public static final URLTemplate DELETE_FOLDER_URL = new URLTemplate("folders/%s?recursive=%b");
054    /**
055     * Folder Info URL Template.
056     */
057    public static final URLTemplate FOLDER_INFO_URL_TEMPLATE = new URLTemplate("folders/%s");
058    /**
059     * Upload File URL Template.
060     */
061    public static final URLTemplate UPLOAD_FILE_URL = new URLTemplate("files/content");
062    /**
063     * Add Collaboration URL Template.
064     */
065    public static final URLTemplate ADD_COLLABORATION_URL = new URLTemplate("collaborations");
066    /**
067     * Get Collaborations URL Template.
068     */
069    public static final URLTemplate GET_COLLABORATIONS_URL = new URLTemplate("folders/%s/collaborations");
070    /**
071     * Get Items URL Template.
072     */
073    public static final URLTemplate GET_ITEMS_URL = new URLTemplate("folders/%s/items/");
074    /**
075     * Search URL Template.
076     */
077    public static final URLTemplate SEARCH_URL_TEMPLATE = new URLTemplate("search");
078    /**
079     * Metadata URL Template.
080     */
081    public static final URLTemplate METADATA_URL_TEMPLATE = new URLTemplate("folders/%s/metadata/%s/%s");
082    /**
083     * Upload Session URL Template.
084     */
085    public static final URLTemplate UPLOAD_SESSION_URL_TEMPLATE = new URLTemplate("files/upload_sessions");
086
087    /**
088     * Constructs a BoxFolder for a folder with a given ID.
089     *
090     * @param api the API connection to be used by the folder.
091     * @param id  the ID of the folder.
092     */
093    public BoxFolder(BoxAPIConnection api, String id) {
094        super(api, id);
095    }
096
097    /**
098     * {@inheritDoc}
099     */
100    @Override
101    protected URL getItemURL() {
102        return FOLDER_INFO_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
103    }
104
105    /**
106     * Gets the current user's root folder.
107     *
108     * @param api the API connection to be used by the folder.
109     * @return the user's root folder.
110     */
111    public static BoxFolder getRootFolder(BoxAPIConnection api) {
112        return new BoxFolder(api, "0");
113    }
114
115    /**
116     * Adds a collaborator to this folder.
117     *
118     * @param collaborator the collaborator to add.
119     * @param role         the role of the collaborator.
120     * @return info about the new collaboration.
121     */
122    public BoxCollaboration.Info collaborate(BoxCollaborator collaborator, BoxCollaboration.Role role) {
123        JsonObject accessibleByField = new JsonObject();
124        accessibleByField.add("id", collaborator.getID());
125
126        if (collaborator instanceof BoxUser) {
127            accessibleByField.add("type", "user");
128        } else if (collaborator instanceof BoxGroup) {
129            accessibleByField.add("type", "group");
130        } else {
131            throw new IllegalArgumentException("The given collaborator is of an unknown type.");
132        }
133
134        return this.collaborate(accessibleByField, role, null, null);
135    }
136
137    /**
138     * Adds a collaborator to this folder. An email will be sent to the collaborator if they don't already have a Box
139     * account.
140     *
141     * @param email the email address of the collaborator to add.
142     * @param role  the role of the collaborator.
143     * @return info about the new collaboration.
144     */
145    public BoxCollaboration.Info collaborate(String email, BoxCollaboration.Role role) {
146        JsonObject accessibleByField = new JsonObject();
147        accessibleByField.add("login", email);
148        accessibleByField.add("type", "user");
149
150        return this.collaborate(accessibleByField, role, null, null);
151    }
152
153    /**
154     * Adds a collaborator to this folder.
155     *
156     * @param collaborator the collaborator to add.
157     * @param role         the role of the collaborator.
158     * @param notify       the user/group should receive email notification of the collaboration or not.
159     * @param canViewPath  the view path collaboration feature is enabled or not.
160     *                     View path collaborations allow the invitee to see the entire ancestral path to the associated
161     *                     folder. The user will not gain privileges in any ancestral folder.
162     * @return info about the new collaboration.
163     */
164    public BoxCollaboration.Info collaborate(BoxCollaborator collaborator, BoxCollaboration.Role role,
165                                             Boolean notify, Boolean canViewPath) {
166        JsonObject accessibleByField = new JsonObject();
167        accessibleByField.add("id", collaborator.getID());
168
169        if (collaborator instanceof BoxUser) {
170            accessibleByField.add("type", "user");
171        } else if (collaborator instanceof BoxGroup) {
172            accessibleByField.add("type", "group");
173        } else {
174            throw new IllegalArgumentException("The given collaborator is of an unknown type.");
175        }
176
177        return this.collaborate(accessibleByField, role, notify, canViewPath);
178    }
179
180    /**
181     * Adds a collaborator to this folder. An email will be sent to the collaborator if they don't already have a Box
182     * account.
183     *
184     * @param email       the email address of the collaborator to add.
185     * @param role        the role of the collaborator.
186     * @param notify      the user/group should receive email notification of the collaboration or not.
187     * @param canViewPath the view path collaboration feature is enabled or not.
188     *                    View path collaborations allow the invitee to see the entire ancestral path to the associated
189     *                    folder. The user will not gain privileges in any ancestral folder.
190     * @return info about the new collaboration.
191     */
192    public BoxCollaboration.Info collaborate(String email, BoxCollaboration.Role role,
193                                             Boolean notify, Boolean canViewPath) {
194        JsonObject accessibleByField = new JsonObject();
195        accessibleByField.add("login", email);
196        accessibleByField.add("type", "user");
197
198        return this.collaborate(accessibleByField, role, notify, canViewPath);
199    }
200
201    private BoxCollaboration.Info collaborate(JsonObject accessibleByField, BoxCollaboration.Role role,
202                                              Boolean notify, Boolean canViewPath) {
203
204        JsonObject itemField = new JsonObject();
205        itemField.add("id", this.getID());
206        itemField.add("type", "folder");
207
208        return BoxCollaboration.create(this.getAPI(), accessibleByField, itemField, role, notify, canViewPath);
209    }
210
211    @Override
212    public BoxSharedLink createSharedLink(BoxSharedLink.Access access, Date unshareDate,
213                                          BoxSharedLink.Permissions permissions) {
214
215        BoxSharedLink sharedLink = new BoxSharedLink(access, unshareDate, permissions);
216        Info info = new Info();
217        info.setSharedLink(sharedLink);
218
219        this.updateInfo(info);
220        return info.getSharedLink();
221    }
222
223    /**
224     * Gets information about all of the collaborations for this folder.
225     *
226     * @return a collection of information about the collaborations for this folder.
227     */
228    public Collection<BoxCollaboration.Info> getCollaborations() {
229        BoxAPIConnection api = this.getAPI();
230        URL url = GET_COLLABORATIONS_URL.build(api.getBaseURL(), this.getID());
231
232        BoxAPIRequest request = new BoxAPIRequest(api, url, "GET");
233        BoxJSONResponse response = (BoxJSONResponse) request.send();
234        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
235
236        int entriesCount = responseJSON.get("total_count").asInt();
237        Collection<BoxCollaboration.Info> collaborations = new ArrayList<BoxCollaboration.Info>(entriesCount);
238        JsonArray entries = responseJSON.get("entries").asArray();
239        for (JsonValue entry : entries) {
240            JsonObject entryObject = entry.asObject();
241            BoxCollaboration collaboration = new BoxCollaboration(api, entryObject.get("id").asString());
242            BoxCollaboration.Info info = collaboration.new Info(entryObject);
243            collaborations.add(info);
244        }
245
246        return collaborations;
247    }
248
249    @Override
250    public BoxFolder.Info getInfo() {
251        URL url = FOLDER_INFO_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
252        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
253        BoxJSONResponse response = (BoxJSONResponse) request.send();
254        return new Info(response.getJSON());
255    }
256
257    @Override
258    public BoxFolder.Info getInfo(String... fields) {
259        String queryString = new QueryStringBuilder().appendParam("fields", fields).toString();
260        URL url = FOLDER_INFO_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
261
262        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
263        BoxJSONResponse response = (BoxJSONResponse) request.send();
264        return new Info(response.getJSON());
265    }
266
267    /**
268     * Updates the information about this folder with any info fields that have been modified locally.
269     *
270     * @param info the updated info.
271     */
272    public void updateInfo(BoxFolder.Info info) {
273        URL url = FOLDER_INFO_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
274        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
275        request.setBody(info.getPendingChanges());
276        BoxJSONResponse response = (BoxJSONResponse) request.send();
277        JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
278        info.update(jsonObject);
279    }
280
281    @Override
282    public BoxFolder.Info copy(BoxFolder destination) {
283        return this.copy(destination, null);
284    }
285
286    @Override
287    public BoxFolder.Info copy(BoxFolder destination, String newName) {
288        URL url = COPY_FOLDER_URL.build(this.getAPI().getBaseURL(), this.getID());
289        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
290
291        JsonObject parent = new JsonObject();
292        parent.add("id", destination.getID());
293
294        JsonObject copyInfo = new JsonObject();
295        copyInfo.add("parent", parent);
296        if (newName != null) {
297            copyInfo.add("name", newName);
298        }
299
300        request.setBody(copyInfo.toString());
301        BoxJSONResponse response = (BoxJSONResponse) request.send();
302        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
303        BoxFolder copiedFolder = new BoxFolder(this.getAPI(), responseJSON.get("id").asString());
304        return copiedFolder.new Info(responseJSON);
305    }
306
307    /**
308     * Creates a new child folder inside this folder.
309     *
310     * @param name the new folder's name.
311     * @return the created folder's info.
312     */
313    public BoxFolder.Info createFolder(String name) {
314        JsonObject parent = new JsonObject();
315        parent.add("id", this.getID());
316
317        JsonObject newFolder = new JsonObject();
318        newFolder.add("name", name);
319        newFolder.add("parent", parent);
320
321        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), CREATE_FOLDER_URL.build(this.getAPI().getBaseURL()),
322                "POST");
323        request.setBody(newFolder.toString());
324        BoxJSONResponse response = (BoxJSONResponse) request.send();
325        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
326
327        BoxFolder createdFolder = new BoxFolder(this.getAPI(), responseJSON.get("id").asString());
328        return createdFolder.new Info(responseJSON);
329    }
330
331    /**
332     * Deletes this folder, optionally recursively deleting all of its contents.
333     *
334     * @param recursive true to recursively delete this folder's contents; otherwise false.
335     */
336    public void delete(boolean recursive) {
337        URL url = DELETE_FOLDER_URL.build(this.getAPI().getBaseURL(), this.getID(), recursive);
338        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE");
339        BoxAPIResponse response = request.send();
340        response.disconnect();
341    }
342
343    @Override
344    public BoxItem.Info move(BoxFolder destination) {
345        return this.move(destination, null);
346    }
347
348    @Override
349    public BoxItem.Info move(BoxFolder destination, String newName) {
350        URL url = FOLDER_INFO_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
351        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
352
353        JsonObject parent = new JsonObject();
354        parent.add("id", destination.getID());
355
356        JsonObject updateInfo = new JsonObject();
357        updateInfo.add("parent", parent);
358        if (newName != null) {
359            updateInfo.add("name", newName);
360        }
361
362        request.setBody(updateInfo.toString());
363        BoxJSONResponse response = (BoxJSONResponse) request.send();
364        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
365        BoxFolder movedFolder = new BoxFolder(this.getAPI(), responseJSON.get("id").asString());
366        return movedFolder.new Info(responseJSON);
367    }
368
369    /**
370     * Renames this folder.
371     *
372     * @param newName the new name of the folder.
373     */
374    public void rename(String newName) {
375        URL url = FOLDER_INFO_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
376        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
377
378        JsonObject updateInfo = new JsonObject();
379        updateInfo.add("name", newName);
380
381        request.setBody(updateInfo.toString());
382        BoxJSONResponse response = (BoxJSONResponse) request.send();
383        response.getJSON();
384    }
385
386    /**
387     * Checks if the file can be successfully uploaded by using the preflight check.
388     *
389     * @param name     the name to give the uploaded file.
390     * @param fileSize the size of the file used for account capacity calculations.
391     */
392    public void canUpload(String name, long fileSize) {
393        URL url = UPLOAD_FILE_URL.build(this.getAPI().getBaseURL());
394        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "OPTIONS");
395
396        JsonObject parent = new JsonObject();
397        parent.add("id", this.getID());
398
399        JsonObject preflightInfo = new JsonObject();
400        preflightInfo.add("parent", parent);
401        preflightInfo.add("name", name);
402
403        preflightInfo.add("size", fileSize);
404
405        request.setBody(preflightInfo.toString());
406        BoxAPIResponse response = request.send();
407        response.disconnect();
408    }
409
410    /**
411     * Uploads a new file to this folder.
412     *
413     * @param fileContent a stream containing the contents of the file to upload.
414     * @param name        the name to give the uploaded file.
415     * @return the uploaded file's info.
416     */
417    public BoxFile.Info uploadFile(InputStream fileContent, String name) {
418        FileUploadParams uploadInfo = new FileUploadParams()
419                .setContent(fileContent)
420                .setName(name);
421        return this.uploadFile(uploadInfo);
422    }
423
424    /**
425     * Uploads a new file to this folder while reporting the progress to a ProgressListener.
426     *
427     * @param fileContent a stream containing the contents of the file to upload.
428     * @param name        the name to give the uploaded file.
429     * @param fileSize    the size of the file used for determining the progress of the upload.
430     * @param listener    a listener for monitoring the upload's progress.
431     * @return the uploaded file's info.
432     */
433    public BoxFile.Info uploadFile(InputStream fileContent, String name, long fileSize, ProgressListener listener) {
434        FileUploadParams uploadInfo = new FileUploadParams()
435                .setContent(fileContent)
436                .setName(name)
437                .setSize(fileSize)
438                .setProgressListener(listener);
439        return this.uploadFile(uploadInfo);
440    }
441
442    /**
443     * Uploads a new file to this folder with custom upload parameters.
444     *
445     * @param uploadParams the custom upload parameters.
446     * @return the uploaded file's info.
447     */
448    public BoxFile.Info uploadFile(FileUploadParams uploadParams) {
449        URL uploadURL = UPLOAD_FILE_URL.build(this.getAPI().getBaseUploadURL());
450        BoxMultipartRequest request = new BoxMultipartRequest(getAPI(), uploadURL);
451
452        JsonObject fieldJSON = new JsonObject();
453        JsonObject parentIdJSON = new JsonObject();
454        parentIdJSON.add("id", getID());
455        fieldJSON.add("name", uploadParams.getName());
456        fieldJSON.add("parent", parentIdJSON);
457
458        if (uploadParams.getCreated() != null) {
459            fieldJSON.add("content_created_at", BoxDateFormat.format(uploadParams.getCreated()));
460        }
461
462        if (uploadParams.getModified() != null) {
463            fieldJSON.add("content_modified_at", BoxDateFormat.format(uploadParams.getModified()));
464        }
465
466        if (uploadParams.getSHA1() != null && !uploadParams.getSHA1().isEmpty()) {
467            request.setContentSHA1(uploadParams.getSHA1());
468        }
469
470        request.putField("attributes", fieldJSON.toString());
471
472        if (uploadParams.getSize() > 0) {
473            request.setFile(uploadParams.getContent(), uploadParams.getName(), uploadParams.getSize());
474        } else {
475            request.setFile(uploadParams.getContent(), uploadParams.getName());
476        }
477
478        BoxJSONResponse response;
479        if (uploadParams.getProgressListener() == null) {
480            response = (BoxJSONResponse) request.send();
481        } else {
482            response = (BoxJSONResponse) request.send(uploadParams.getProgressListener());
483        }
484        JsonObject collection = JsonObject.readFrom(response.getJSON());
485        JsonArray entries = collection.get("entries").asArray();
486        JsonObject fileInfoJSON = entries.get(0).asObject();
487        String uploadedFileID = fileInfoJSON.get("id").asString();
488
489        BoxFile uploadedFile = new BoxFile(getAPI(), uploadedFileID);
490        return uploadedFile.new Info(fileInfoJSON);
491    }
492
493    /**
494     * Uploads a new weblink to this folder.
495     *
496     * @param linkURL the URL the weblink points to.
497     * @return the uploaded weblink's info.
498     */
499    public BoxWebLink.Info createWebLink(URL linkURL) {
500        return this.createWebLink(null, linkURL,
501                null);
502    }
503
504    /**
505     * Uploads a new weblink to this folder.
506     *
507     * @param name    the filename for the weblink.
508     * @param linkURL the URL the weblink points to.
509     * @return the uploaded weblink's info.
510     */
511    public BoxWebLink.Info createWebLink(String name, URL linkURL) {
512        return this.createWebLink(name, linkURL,
513                null);
514    }
515
516    /**
517     * Uploads a new weblink to this folder.
518     *
519     * @param linkURL     the URL the weblink points to.
520     * @param description the weblink's description.
521     * @return the uploaded weblink's info.
522     */
523    public BoxWebLink.Info createWebLink(URL linkURL, String description) {
524        return this.createWebLink(null, linkURL, description);
525    }
526
527    /**
528     * Uploads a new weblink to this folder.
529     *
530     * @param name        the filename for the weblink.
531     * @param linkURL     the URL the weblink points to.
532     * @param description the weblink's description.
533     * @return the uploaded weblink's info.
534     */
535    public BoxWebLink.Info createWebLink(String name, URL linkURL, String description) {
536        JsonObject parent = new JsonObject();
537        parent.add("id", this.getID());
538
539        JsonObject newWebLink = new JsonObject();
540        newWebLink.add("name", name);
541        newWebLink.add("parent", parent);
542        newWebLink.add("url", linkURL.toString());
543
544        if (description != null) {
545            newWebLink.add("description", description);
546        }
547
548        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(),
549                CREATE_WEB_LINK_URL.build(this.getAPI().getBaseURL()), "POST");
550        request.setBody(newWebLink.toString());
551        BoxJSONResponse response = (BoxJSONResponse) request.send();
552        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
553
554        BoxWebLink createdWebLink = new BoxWebLink(this.getAPI(), responseJSON.get("id").asString());
555        return createdWebLink.new Info(responseJSON);
556    }
557
558    /**
559     * Returns an iterable containing the items in this folder. Iterating over the iterable returned by this method is
560     * equivalent to iterating over this BoxFolder directly.
561     *
562     * @return an iterable containing the items in this folder.
563     */
564    public Iterable<BoxItem.Info> getChildren() {
565        return this;
566    }
567
568    /**
569     * Returns an iterable containing the items in this folder and specifies which child fields to retrieve from the
570     * API.
571     *
572     * @param fields the fields to retrieve.
573     * @return an iterable containing the items in this folder.
574     */
575    public Iterable<BoxItem.Info> getChildren(final String... fields) {
576        return new Iterable<BoxItem.Info>() {
577            @Override
578            public Iterator<BoxItem.Info> iterator() {
579                String queryString = new QueryStringBuilder().appendParam("fields", fields).toString();
580                URL url = GET_ITEMS_URL.buildWithQuery(getAPI().getBaseURL(), queryString, getID());
581                return new BoxItemIterator(getAPI(), url);
582            }
583        };
584    }
585
586    /**
587     * Retrieves a specific range of child items in this folder.
588     *
589     * @param offset the index of the first child item to retrieve.
590     * @param limit  the maximum number of children to retrieve after the offset.
591     * @param fields the fields to retrieve.
592     * @return a partial collection containing the specified range of child items.
593     */
594    public PartialCollection<BoxItem.Info> getChildrenRange(long offset, long limit, String... fields) {
595        QueryStringBuilder builder = new QueryStringBuilder()
596                .appendParam("limit", limit)
597                .appendParam("offset", offset);
598
599        if (fields.length > 0) {
600            builder.appendParam("fields", fields).toString();
601        }
602
603        URL url = GET_ITEMS_URL.buildWithQuery(getAPI().getBaseURL(), builder.toString(), getID());
604        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
605        BoxJSONResponse response = (BoxJSONResponse) request.send();
606        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
607
608        String totalCountString = responseJSON.get("total_count").toString();
609        long fullSize = Double.valueOf(totalCountString).longValue();
610        PartialCollection<BoxItem.Info> children = new PartialCollection<BoxItem.Info>(offset, limit, fullSize);
611        JsonArray jsonArray = responseJSON.get("entries").asArray();
612        for (JsonValue value : jsonArray) {
613            JsonObject jsonObject = value.asObject();
614            BoxItem.Info parsedItemInfo = (BoxItem.Info) BoxResource.parseInfo(this.getAPI(), jsonObject);
615            if (parsedItemInfo != null) {
616                children.add(parsedItemInfo);
617            }
618        }
619        return children;
620    }
621
622    /**
623     * Returns an iterator over the items in this folder.
624     *
625     * @return an iterator over the items in this folder.
626     */
627    @Override
628    public Iterator<BoxItem.Info> iterator() {
629        URL url = GET_ITEMS_URL.build(this.getAPI().getBaseURL(), BoxFolder.this.getID());
630        return new BoxItemIterator(BoxFolder.this.getAPI(), url);
631    }
632
633    /**
634     * Adds new {@link BoxWebHook} to this {@link BoxFolder}.
635     *
636     * @param address  {@link BoxWebHook.Info#getAddress()}
637     * @param triggers {@link BoxWebHook.Info#getTriggers()}
638     * @return created {@link BoxWebHook.Info}
639     */
640    public BoxWebHook.Info addWebHook(URL address, BoxWebHook.Trigger... triggers) {
641        return BoxWebHook.create(this, address, triggers);
642    }
643
644    /**
645     * Used to retrieve the watermark for the folder.
646     * If the folder does not have a watermark applied to it, a 404 Not Found will be returned by API.
647     *
648     * @param fields the fields to retrieve.
649     * @return the watermark associated with the folder.
650     */
651    public BoxWatermark getWatermark(String... fields) {
652        return this.getWatermark(FOLDER_INFO_URL_TEMPLATE, fields);
653    }
654
655    /**
656     * Used to apply or update the watermark for the folder.
657     *
658     * @return the watermark associated with the folder.
659     */
660    public BoxWatermark applyWatermark() {
661        return this.applyWatermark(FOLDER_INFO_URL_TEMPLATE, BoxWatermark.WATERMARK_DEFAULT_IMPRINT);
662    }
663
664    /**
665     * Removes a watermark from the folder.
666     * If the folder did not have a watermark applied to it, a 404 Not Found will be returned by API.
667     */
668    public void removeWatermark() {
669        this.removeWatermark(FOLDER_INFO_URL_TEMPLATE);
670    }
671
672    /**
673     * Used to retrieve all metadata associated with the folder.
674     *
675     * @param fields the optional fields to retrieve.
676     * @return An iterable of metadata instances associated with the folder
677     */
678    public Iterable<Metadata> getAllMetadata(String... fields) {
679        return Metadata.getAllMetadata(this, fields);
680    }
681
682    /**
683     * This method is deprecated, please use the {@link BoxSearch} class instead.
684     * Searches this folder and all descendant folders using a given queryPlease use BoxSearch Instead.
685     *
686     * @param query the search query.
687     * @return an Iterable containing the search results.
688     */
689    @Deprecated
690    public Iterable<BoxItem.Info> search(final String query) {
691        return new Iterable<BoxItem.Info>() {
692            @Override
693            public Iterator<BoxItem.Info> iterator() {
694                QueryStringBuilder builder = new QueryStringBuilder();
695                builder.appendParam("query", query);
696                builder.appendParam("ancestor_folder_ids", getID());
697
698                URL url = SEARCH_URL_TEMPLATE.buildWithQuery(getAPI().getBaseURL(), builder.toString());
699                return new BoxItemIterator(getAPI(), url);
700            }
701        };
702    }
703
704    @Override
705    public BoxFolder.Info setCollections(BoxCollection... collections) {
706        JsonArray jsonArray = new JsonArray();
707        for (BoxCollection collection : collections) {
708            JsonObject collectionJSON = new JsonObject();
709            collectionJSON.add("id", collection.getID());
710            jsonArray.add(collectionJSON);
711        }
712        JsonObject infoJSON = new JsonObject();
713        infoJSON.add("collections", jsonArray);
714
715        String queryString = new QueryStringBuilder().appendParam("fields", ALL_FIELDS).toString();
716        URL url = FOLDER_INFO_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
717        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
718        request.setBody(infoJSON.toString());
719        BoxJSONResponse response = (BoxJSONResponse) request.send();
720        JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
721        return new Info(jsonObject);
722    }
723
724    /**
725     * Creates global property metadata on this folder.
726     *
727     * @param metadata the new metadata values.
728     * @return the metadata returned from the server.
729     */
730    public Metadata createMetadata(Metadata metadata) {
731        return this.createMetadata(Metadata.DEFAULT_METADATA_TYPE, metadata);
732    }
733
734    /**
735     * Creates metadata on this folder using a specified template.
736     *
737     * @param templateName the name of the metadata template.
738     * @param metadata     the new metadata values.
739     * @return the metadata returned from the server.
740     */
741    public Metadata createMetadata(String templateName, Metadata metadata) {
742        String scope = Metadata.scopeBasedOnType(templateName);
743        return this.createMetadata(templateName, scope, metadata);
744    }
745
746    /**
747     * Creates metadata on this folder using a specified scope and template.
748     *
749     * @param templateName the name of the metadata template.
750     * @param scope        the scope of the template (usually "global" or "enterprise").
751     * @param metadata     the new metadata values.
752     * @return the metadata returned from the server.
753     */
754    public Metadata createMetadata(String templateName, String scope, Metadata metadata) {
755        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), scope, templateName);
756        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "POST");
757        request.addHeader("Content-Type", "application/json");
758        request.setBody(metadata.toString());
759        BoxJSONResponse response = (BoxJSONResponse) request.send();
760        return new Metadata(JsonObject.readFrom(response.getJSON()));
761    }
762
763    /**
764     * Gets the global properties metadata on this folder.
765     *
766     * @return the metadata returned from the server.
767     */
768    public Metadata getMetadata() {
769        return this.getMetadata(Metadata.DEFAULT_METADATA_TYPE);
770    }
771
772    /**
773     * Gets the metadata on this folder associated with a specified template.
774     *
775     * @param templateName the metadata template type name.
776     * @return the metadata returned from the server.
777     */
778    public Metadata getMetadata(String templateName) {
779        String scope = Metadata.scopeBasedOnType(templateName);
780        return this.getMetadata(templateName, scope);
781    }
782
783    /**
784     * Gets the metadata on this folder associated with a specified scope and template.
785     *
786     * @param templateName the metadata template type name.
787     * @param scope        the scope of the template (usually "global" or "enterprise").
788     * @return the metadata returned from the server.
789     */
790    public Metadata getMetadata(String templateName, String scope) {
791        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), scope, templateName);
792        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
793        BoxJSONResponse response = (BoxJSONResponse) request.send();
794        return new Metadata(JsonObject.readFrom(response.getJSON()));
795    }
796
797    /**
798     * Updates the global properties metadata on this folder.
799     *
800     * @param metadata the new metadata values.
801     * @return the metadata returned from the server.
802     */
803    public Metadata updateMetadata(Metadata metadata) {
804        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), metadata.getScope(),
805                metadata.getTemplateName());
806        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "PUT");
807        request.addHeader("Content-Type", "application/json-patch+json");
808        request.setBody(metadata.getPatch());
809        BoxJSONResponse response = (BoxJSONResponse) request.send();
810        return new Metadata(JsonObject.readFrom(response.getJSON()));
811    }
812
813    /**
814     * Deletes the global properties metadata on this folder.
815     */
816    public void deleteMetadata() {
817        this.deleteMetadata(Metadata.DEFAULT_METADATA_TYPE);
818    }
819
820    /**
821     * Deletes the metadata on this folder associated with a specified template.
822     *
823     * @param templateName the metadata template type name.
824     */
825    public void deleteMetadata(String templateName) {
826        String scope = Metadata.scopeBasedOnType(templateName);
827        this.deleteMetadata(templateName, scope);
828    }
829
830    /**
831     * Deletes the metadata on this folder associated with a specified scope and template.
832     *
833     * @param templateName the metadata template type name.
834     * @param scope        the scope of the template (usually "global" or "enterprise").
835     */
836    public void deleteMetadata(String templateName, String scope) {
837        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), scope, templateName);
838        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE");
839        BoxAPIResponse response = request.send();
840        response.disconnect();
841    }
842
843    /**
844     * Creates an upload session to create a new file in chunks.
845     * This will first verify that the file can be created and then open a session for uploading pieces of the file.
846     *
847     * @param fileName the name of the file to be created
848     * @param fileSize the size of the file that will be uploaded
849     * @return the created upload session instance
850     */
851    public BoxFileUploadSession.Info createUploadSession(String fileName, long fileSize) {
852
853        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL());
854        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
855
856        JsonObject body = new JsonObject();
857        body.add("folder_id", this.getID());
858        body.add("file_name", fileName);
859        body.add("file_size", fileSize);
860        request.setBody(body.toString());
861
862        BoxJSONResponse response = (BoxJSONResponse) request.send();
863        JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
864
865        String sessionId = jsonObject.get("id").asString();
866        BoxFileUploadSession session = new BoxFileUploadSession(this.getAPI(), sessionId);
867
868        return session.new Info(jsonObject);
869    }
870
871    /**
872     * Creates a new file.
873     *
874     * @param inputStream the stream instance that contains the data.
875     * @param fileName    the name of the file to be created.
876     * @param fileSize    the size of the file that will be uploaded.
877     * @return the created file instance.
878     * @throws InterruptedException when a thread execution is interrupted.
879     * @throws IOException          when reading a stream throws exception.
880     */
881    public BoxFile.Info uploadLargeFile(InputStream inputStream, String fileName, long fileSize)
882            throws InterruptedException, IOException {
883        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL());
884        return new LargeFileUpload().
885                upload(this.getAPI(), this.getID(), inputStream, url, fileName, fileSize);
886    }
887
888    /**
889     * Creates a new file using specified number of parallel http connections.
890     *
891     * @param inputStream          the stream instance that contains the data.
892     * @param fileName             the name of the file to be created.
893     * @param fileSize             the size of the file that will be uploaded.
894     * @param nParallelConnections number of parallel http connections to use
895     * @param timeOut              time to wait before killing the job
896     * @param unit                 time unit for the time wait value
897     * @return the created file instance.
898     * @throws InterruptedException when a thread execution is interrupted.
899     * @throws IOException          when reading a stream throws exception.
900     */
901    public BoxFile.Info uploadLargeFile(InputStream inputStream, String fileName, long fileSize,
902                                        int nParallelConnections, long timeOut, TimeUnit unit)
903            throws InterruptedException, IOException {
904        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL());
905        return new LargeFileUpload(nParallelConnections, timeOut, unit).
906                upload(this.getAPI(), this.getID(), inputStream, url, fileName, fileSize);
907    }
908
909    /**
910     * Contains information about a BoxFolder.
911     */
912    public class Info extends BoxItem.Info {
913        private BoxUploadEmail uploadEmail;
914        private boolean hasCollaborations;
915        private SyncState syncState;
916        private EnumSet<Permission> permissions;
917        private boolean canNonOwnersInvite;
918        private boolean isWatermarked;
919        private Map<String, Map<String, Metadata>> metadataMap;
920
921        /**
922         * Constructs an empty Info object.
923         */
924        public Info() {
925            super();
926        }
927
928        /**
929         * Constructs an Info object by parsing information from a JSON string.
930         *
931         * @param json the JSON string to parse.
932         */
933        public Info(String json) {
934            super(json);
935        }
936
937        /**
938         * Constructs an Info object using an already parsed JSON object.
939         *
940         * @param jsonObject the parsed JSON object.
941         */
942        public Info(JsonObject jsonObject) {
943            super(jsonObject);
944        }
945
946        /**
947         * Gets the upload email for the folder.
948         *
949         * @return the upload email for the folder.
950         */
951        public BoxUploadEmail getUploadEmail() {
952            return this.uploadEmail;
953        }
954
955        /**
956         * Sets the upload email for the folder.
957         *
958         * @param uploadEmail the upload email for the folder.
959         */
960        public void setUploadEmail(BoxUploadEmail uploadEmail) {
961            if (this.uploadEmail == uploadEmail) {
962                return;
963            }
964
965            this.removeChildObject("folder_upload_email");
966            this.uploadEmail = uploadEmail;
967
968            if (uploadEmail == null) {
969                this.addPendingChange("folder_upload_email", (String) null);
970            } else {
971                this.addChildObject("folder_upload_email", uploadEmail);
972            }
973        }
974
975        /**
976         * Gets whether or not the folder has any collaborations.
977         *
978         * @return true if the folder has collaborations; otherwise false.
979         */
980        public boolean getHasCollaborations() {
981            return this.hasCollaborations;
982        }
983
984        /**
985         * Gets the sync state of the folder.
986         *
987         * @return the sync state of the folder.
988         */
989        public SyncState getSyncState() {
990            return this.syncState;
991        }
992
993        /**
994         * Sets the sync state of the folder.
995         *
996         * @param syncState the sync state of the folder.
997         */
998        public void setSyncState(SyncState syncState) {
999            this.syncState = syncState;
1000            this.addPendingChange("sync_state", syncState.toJSONValue());
1001        }
1002
1003        /**
1004         * Gets the permissions that the current user has on the folder.
1005         *
1006         * @return the permissions that the current user has on the folder.
1007         */
1008        public EnumSet<Permission> getPermissions() {
1009            return this.permissions;
1010        }
1011
1012        /**
1013         * Gets whether or not the non-owners can invite collaborators to the folder.
1014         *
1015         * @return [description]
1016         */
1017        public boolean getCanNonOwnersInvite() {
1018            return this.canNonOwnersInvite;
1019        }
1020
1021        /**
1022         * Gets flag indicating whether this file is Watermarked.
1023         *
1024         * @return whether the file is watermarked or not
1025         */
1026        public boolean getIsWatermarked() {
1027            return this.isWatermarked;
1028        }
1029
1030        /**
1031         * Gets the metadata on this folder associated with a specified scope and template.
1032         * Makes an attempt to get metadata that was retrieved using getInfo(String ...) method. If no result is found
1033         * then makes an API call to get metadata
1034         *
1035         * @param templateName the metadata template type name.
1036         * @param scope        the scope of the template (usually "global" or "enterprise").
1037         * @return the metadata returned from the server.
1038         */
1039        public Metadata getMetadata(String templateName, String scope) {
1040            try {
1041                return this.metadataMap.get(scope).get(templateName);
1042            } catch (NullPointerException e) {
1043                return null;
1044            }
1045        }
1046
1047        @Override
1048        public BoxFolder getResource() {
1049            return BoxFolder.this;
1050        }
1051
1052        @Override
1053        protected void parseJSONMember(JsonObject.Member member) {
1054            super.parseJSONMember(member);
1055
1056            String memberName = member.getName();
1057            JsonValue value = member.getValue();
1058            if (memberName.equals("folder_upload_email")) {
1059                if (this.uploadEmail == null) {
1060                    this.uploadEmail = new BoxUploadEmail(value.asObject());
1061                } else {
1062                    this.uploadEmail.update(value.asObject());
1063                }
1064
1065            } else if (memberName.equals("has_collaborations")) {
1066                this.hasCollaborations = value.asBoolean();
1067
1068            } else if (memberName.equals("sync_state")) {
1069                this.syncState = SyncState.fromJSONValue(value.asString());
1070
1071            } else if (memberName.equals("permissions")) {
1072                this.permissions = this.parsePermissions(value.asObject());
1073
1074            } else if (memberName.equals("can_non_owners_invite")) {
1075                this.canNonOwnersInvite = value.asBoolean();
1076            } else if (memberName.equals("watermark_info")) {
1077                JsonObject jsonObject = value.asObject();
1078                this.isWatermarked = jsonObject.get("is_watermarked").asBoolean();
1079            } else if (memberName.equals("metadata")) {
1080                JsonObject jsonObject = value.asObject();
1081                this.metadataMap = Parsers.parseAndPopulateMetadataMap(jsonObject);
1082            }
1083        }
1084
1085        private EnumSet<Permission> parsePermissions(JsonObject jsonObject) {
1086            EnumSet<Permission> permissions = EnumSet.noneOf(Permission.class);
1087            for (JsonObject.Member member : jsonObject) {
1088                JsonValue value = member.getValue();
1089                if (value.isNull() || !value.asBoolean()) {
1090                    continue;
1091                }
1092
1093                String memberName = member.getName();
1094                if (memberName.equals("can_download")) {
1095                    permissions.add(Permission.CAN_DOWNLOAD);
1096                } else if (memberName.equals("can_upload")) {
1097                    permissions.add(Permission.CAN_UPLOAD);
1098                } else if (memberName.equals("can_rename")) {
1099                    permissions.add(Permission.CAN_RENAME);
1100                } else if (memberName.equals("can_delete")) {
1101                    permissions.add(Permission.CAN_DELETE);
1102                } else if (memberName.equals("can_share")) {
1103                    permissions.add(Permission.CAN_SHARE);
1104                } else if (memberName.equals("can_invite_collaborator")) {
1105                    permissions.add(Permission.CAN_INVITE_COLLABORATOR);
1106                } else if (memberName.equals("can_set_share_access")) {
1107                    permissions.add(Permission.CAN_SET_SHARE_ACCESS);
1108                }
1109            }
1110
1111            return permissions;
1112        }
1113    }
1114
1115    /**
1116     * Enumerates the possible sync states that a folder can have.
1117     */
1118    public enum SyncState {
1119        /**
1120         * The folder is synced.
1121         */
1122        SYNCED("synced"),
1123
1124        /**
1125         * The folder is not synced.
1126         */
1127        NOT_SYNCED("not_synced"),
1128
1129        /**
1130         * The folder is partially synced.
1131         */
1132        PARTIALLY_SYNCED("partially_synced");
1133
1134        private final String jsonValue;
1135
1136        private SyncState(String jsonValue) {
1137            this.jsonValue = jsonValue;
1138        }
1139
1140        static SyncState fromJSONValue(String jsonValue) {
1141            return SyncState.valueOf(jsonValue.toUpperCase());
1142        }
1143
1144        String toJSONValue() {
1145            return this.jsonValue;
1146        }
1147    }
1148
1149    /**
1150     * Enumerates the possible permissions that a user can have on a folder.
1151     */
1152    public enum Permission {
1153        /**
1154         * The user can download the folder.
1155         */
1156        CAN_DOWNLOAD("can_download"),
1157
1158        /**
1159         * The user can upload to the folder.
1160         */
1161        CAN_UPLOAD("can_upload"),
1162
1163        /**
1164         * The user can rename the folder.
1165         */
1166        CAN_RENAME("can_rename"),
1167
1168        /**
1169         * The user can delete the folder.
1170         */
1171        CAN_DELETE("can_delete"),
1172
1173        /**
1174         * The user can share the folder.
1175         */
1176        CAN_SHARE("can_share"),
1177
1178        /**
1179         * The user can invite collaborators to the folder.
1180         */
1181        CAN_INVITE_COLLABORATOR("can_invite_collaborator"),
1182
1183        /**
1184         * The user can set the access level for shared links to the folder.
1185         */
1186        CAN_SET_SHARE_ACCESS("can_set_share_access");
1187
1188        private final String jsonValue;
1189
1190        private Permission(String jsonValue) {
1191            this.jsonValue = jsonValue;
1192        }
1193
1194        static Permission fromJSONValue(String jsonValue) {
1195            return Permission.valueOf(jsonValue.toUpperCase());
1196        }
1197
1198        String toJSONValue() {
1199            return this.jsonValue;
1200        }
1201    }
1202}