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