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