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