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        if (uploadParams.getSHA1() != null && !uploadParams.getSHA1().isEmpty()) {
497            request.setContentSHA1(uploadParams.getSHA1());
498        }
499
500        request.putField("attributes", fieldJSON.toString());
501
502        if (uploadParams.getSize() > 0) {
503            request.setFile(uploadParams.getContent(), uploadParams.getName(), uploadParams.getSize());
504        } else {
505            request.setFile(uploadParams.getContent(), uploadParams.getName());
506        }
507
508        BoxJSONResponse response;
509        if (uploadParams.getProgressListener() == null) {
510            response = (BoxJSONResponse) request.send();
511        } else {
512            response = (BoxJSONResponse) request.send(uploadParams.getProgressListener());
513        }
514        JsonObject collection = JsonObject.readFrom(response.getJSON());
515        JsonArray entries = collection.get("entries").asArray();
516        JsonObject fileInfoJSON = entries.get(0).asObject();
517        String uploadedFileID = fileInfoJSON.get("id").asString();
518
519        BoxFile uploadedFile = new BoxFile(getAPI(), uploadedFileID);
520        return uploadedFile.new Info(fileInfoJSON);
521    }
522
523    /**
524     * Uploads a new weblink to this folder.
525     * @param  linkURL     the URL the weblink points to.
526     * @return             the uploaded weblink's info.
527     */
528    public BoxWebLink.Info createWebLink(URL linkURL) {
529        return this.createWebLink(null, linkURL, null);
530    }
531
532    /**
533     * Uploads a new weblink to this folder.
534     * @param  name        the filename for the weblink.
535     * @param  linkURL     the URL the weblink points to.
536     * @return             the uploaded weblink's info.
537     */
538    public BoxWebLink.Info createWebLink(String name, URL linkURL) {
539        return this.createWebLink(name, linkURL, null);
540    }
541
542    /**
543     * Uploads a new weblink to this folder.
544     * @param  linkURL     the URL the weblink points to.
545     * @param  description the weblink's description.
546     * @return             the uploaded weblink's info.
547     */
548    public BoxWebLink.Info createWebLink(URL linkURL, String description) {
549        return this.createWebLink(null, linkURL, description);
550    }
551
552    /**
553     * Uploads a new weblink to this folder.
554     * @param  name        the filename for the weblink.
555     * @param  linkURL     the URL the weblink points to.
556     * @param  description the weblink's description.
557     * @return             the uploaded weblink's info.
558     */
559    public BoxWebLink.Info createWebLink(String name, URL linkURL, String description) {
560        JsonObject parent = new JsonObject();
561        parent.add("id", this.getID());
562
563        JsonObject newWebLink = new JsonObject();
564        newWebLink.add("name", name);
565        newWebLink.add("parent", parent);
566        newWebLink.add("url", linkURL.toString());
567
568        if (description != null) {
569            newWebLink.add("description", description);
570        }
571
572        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(),
573            CREATE_WEB_LINK_URL.build(this.getAPI().getBaseURL()), "POST");
574        request.setBody(newWebLink.toString());
575        BoxJSONResponse response = (BoxJSONResponse) request.send();
576        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
577
578        BoxWebLink createdWebLink = new BoxWebLink(this.getAPI(), responseJSON.get("id").asString());
579        return createdWebLink.new Info(responseJSON);
580    }
581
582    /**
583     * Returns an iterable containing the items in this folder. Iterating over the iterable returned by this method is
584     * equivalent to iterating over this BoxFolder directly.
585     * @return an iterable containing the items in this folder.
586     */
587    public Iterable<BoxItem.Info> getChildren() {
588        return this;
589    }
590
591    /**
592     * Returns an iterable containing the items in this folder and specifies which child fields to retrieve from the
593     * API.
594     * @param  fields the fields to retrieve.
595     * @return        an iterable containing the items in this folder.
596     */
597    public Iterable<BoxItem.Info> getChildren(final String... fields) {
598        return new Iterable<BoxItem.Info>() {
599            @Override
600            public Iterator<BoxItem.Info> iterator() {
601                String queryString = new QueryStringBuilder().appendParam("fields", fields).toString();
602                URL url = GET_ITEMS_URL.buildWithQuery(getAPI().getBaseURL(), queryString, getID());
603                return new BoxItemIterator(getAPI(), url);
604            }
605        };
606    }
607
608    /**
609     * Retrieves a specific range of child items in this folder.
610     * @param  offset the index of the first child item to retrieve.
611     * @param  limit  the maximum number of children to retrieve after the offset.
612     * @param  fields the fields to retrieve.
613     * @return        a partial collection containing the specified range of child items.
614     */
615    public PartialCollection<BoxItem.Info> getChildrenRange(long offset, long limit, String... fields) {
616        QueryStringBuilder builder = new QueryStringBuilder()
617            .appendParam("limit", limit)
618            .appendParam("offset", offset);
619
620        if (fields.length > 0) {
621            builder.appendParam("fields", fields).toString();
622        }
623
624        URL url = GET_ITEMS_URL.buildWithQuery(getAPI().getBaseURL(), builder.toString(), getID());
625        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
626        BoxJSONResponse response = (BoxJSONResponse) request.send();
627        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
628
629        String totalCountString = responseJSON.get("total_count").toString();
630        long fullSize = Double.valueOf(totalCountString).longValue();
631        PartialCollection<BoxItem.Info> children = new PartialCollection<BoxItem.Info>(offset, limit, fullSize);
632        JsonArray jsonArray = responseJSON.get("entries").asArray();
633        for (JsonValue value : jsonArray) {
634            JsonObject jsonObject = value.asObject();
635            BoxItem.Info parsedItemInfo = (BoxItem.Info) BoxResource.parseInfo(this.getAPI(), jsonObject);
636            if (parsedItemInfo != null) {
637                children.add(parsedItemInfo);
638            }
639        }
640        return children;
641    }
642
643    /**
644     * Returns an iterator over the items in this folder.
645     * @return an iterator over the items in this folder.
646     */
647    @Override
648    public Iterator<BoxItem.Info> iterator() {
649        URL url = GET_ITEMS_URL.build(this.getAPI().getBaseURL(), BoxFolder.this.getID());
650        return new BoxItemIterator(BoxFolder.this.getAPI(), url);
651    }
652
653    /**
654     * Adds new {@link BoxWebHook} to this {@link BoxFolder}.
655     *
656     * @param address
657     *            {@link BoxWebHook.Info#getAddress()}
658     * @param triggers
659     *            {@link BoxWebHook.Info#getTriggers()}
660     * @return created {@link BoxWebHook.Info}
661     */
662    public BoxWebHook.Info addWebHook(URL address, BoxWebHook.Trigger... triggers) {
663        return BoxWebHook.create(this, address, triggers);
664    }
665
666    /**
667
668     * Used to retrieve the watermark for the folder.
669     * If the folder does not have a watermark applied to it, a 404 Not Found will be returned by API.
670     * @param fields the fields to retrieve.
671     * @return the watermark associated with the folder.
672     */
673    public BoxWatermark getWatermark(String... fields) {
674        return this.getWatermark(FOLDER_INFO_URL_TEMPLATE, fields);
675    }
676
677    /**
678     * Used to apply or update the watermark for the folder.
679     * @return the watermark associated with the folder.
680     */
681    public BoxWatermark applyWatermark() {
682        return this.applyWatermark(FOLDER_INFO_URL_TEMPLATE, BoxWatermark.WATERMARK_DEFAULT_IMPRINT);
683    }
684
685    /**
686     * Removes a watermark from the folder.
687     * If the folder did not have a watermark applied to it, a 404 Not Found will be returned by API.
688     */
689    public void removeWatermark() {
690        this.removeWatermark(FOLDER_INFO_URL_TEMPLATE);
691    }
692
693    /**
694     * Used to retrieve all metadata associated with the folder.
695     *
696     * @param fields the optional fields to retrieve.
697     * @return An iterable of metadata instances associated with the folder
698     */
699    public Iterable<Metadata> getAllMetadata(String... fields) {
700        return Metadata.getAllMetadata(this, fields);
701    }
702
703    /**
704     * This method is deprecated, please use the {@link BoxSearch} class instead.
705     * Searches this folder and all descendant folders using a given queryPlease use BoxSearch Instead.
706     * @param  query the search query.
707     * @return an Iterable containing the search results.
708     */
709    @Deprecated
710    public Iterable<BoxItem.Info> search(final String query) {
711        return new Iterable<BoxItem.Info>() {
712            @Override
713            public Iterator<BoxItem.Info> iterator() {
714                QueryStringBuilder builder = new QueryStringBuilder();
715                builder.appendParam("query", query);
716                builder.appendParam("ancestor_folder_ids", getID());
717
718                URL url = SEARCH_URL_TEMPLATE.buildWithQuery(getAPI().getBaseURL(), builder.toString());
719                return new BoxItemIterator(getAPI(), url);
720            }
721        };
722    }
723
724    @Override
725    public BoxFolder.Info setCollections(BoxCollection... collections) {
726        JsonArray jsonArray = new JsonArray();
727        for (BoxCollection collection : collections) {
728            JsonObject collectionJSON = new JsonObject();
729            collectionJSON.add("id", collection.getID());
730            jsonArray.add(collectionJSON);
731        }
732        JsonObject infoJSON = new JsonObject();
733        infoJSON.add("collections", jsonArray);
734
735        String queryString = new QueryStringBuilder().appendParam("fields", ALL_FIELDS).toString();
736        URL url = FOLDER_INFO_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
737        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "PUT");
738        request.setBody(infoJSON.toString());
739        BoxJSONResponse response = (BoxJSONResponse) request.send();
740        JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
741        return new Info(jsonObject);
742    }
743
744    /**
745     * Creates global property metadata on this folder.
746     * @param   metadata    the new metadata values.
747     * @return              the metadata returned from the server.
748     */
749    public Metadata createMetadata(Metadata metadata) {
750        return this.createMetadata(Metadata.DEFAULT_METADATA_TYPE, metadata);
751    }
752
753    /**
754     * Creates metadata on this folder using a specified template.
755     * @param   templateName    the name of the metadata template.
756     * @param   metadata        the new metadata values.
757     * @return                  the metadata returned from the server.
758     */
759    public Metadata createMetadata(String templateName, Metadata metadata) {
760        String scope = Metadata.scopeBasedOnType(templateName);
761        return this.createMetadata(templateName, scope, metadata);
762    }
763
764    /**
765     * Creates metadata on this folder using a specified scope and template.
766     * @param   templateName    the name of the metadata template.
767     * @param   scope           the scope of the template (usually "global" or "enterprise").
768     * @param   metadata        the new metadata values.
769     * @return                  the metadata returned from the server.
770     */
771    public Metadata createMetadata(String templateName, String scope, Metadata metadata) {
772        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), scope, templateName);
773        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "POST");
774        request.addHeader("Content-Type", "application/json");
775        request.setBody(metadata.toString());
776        BoxJSONResponse response = (BoxJSONResponse) request.send();
777        return new Metadata(JsonObject.readFrom(response.getJSON()));
778    }
779
780    /**
781     * Gets the global properties metadata on this folder.
782     * @return the metadata returned from the server.
783     */
784    public Metadata getMetadata() {
785        return this.getMetadata(Metadata.DEFAULT_METADATA_TYPE);
786    }
787
788    /**
789     * Gets the metadata on this folder associated with a specified template.
790     * @param   templateName    the metadata template type name.
791     * @return                  the metadata returned from the server.
792     */
793    public Metadata getMetadata(String templateName) {
794        String scope = Metadata.scopeBasedOnType(templateName);
795        return this.getMetadata(templateName, scope);
796    }
797
798    /**
799     * Gets the metadata on this folder associated with a specified scope and template.
800     * @param   templateName    the metadata template type name.
801     * @param   scope           the scope of the template (usually "global" or "enterprise").
802     * @return                  the metadata returned from the server.
803     */
804    public Metadata getMetadata(String templateName, String scope) {
805        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), scope, templateName);
806        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
807        BoxJSONResponse response = (BoxJSONResponse) request.send();
808        return new Metadata(JsonObject.readFrom(response.getJSON()));
809    }
810
811    /**
812     * Updates the global properties metadata on this folder.
813     * @param   metadata    the new metadata values.
814     * @return              the metadata returned from the server.
815     */
816    public Metadata updateMetadata(Metadata metadata) {
817        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), metadata.getScope(),
818            metadata.getTemplateName());
819        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "PUT");
820        request.addHeader("Content-Type", "application/json-patch+json");
821        request.setBody(metadata.getPatch());
822        BoxJSONResponse response = (BoxJSONResponse) request.send();
823        return new Metadata(JsonObject.readFrom(response.getJSON()));
824    }
825
826    /**
827     * Deletes the global properties metadata on this folder.
828     */
829    public void deleteMetadata() {
830        this.deleteMetadata(Metadata.DEFAULT_METADATA_TYPE);
831    }
832
833    /**
834     * Deletes the metadata on this folder associated with a specified template.
835     * @param templateName the metadata template type name.
836     */
837    public void deleteMetadata(String templateName) {
838        String scope = Metadata.scopeBasedOnType(templateName);
839        this.deleteMetadata(templateName, scope);
840    }
841
842    /**
843     * Deletes the metadata on this folder associated with a specified scope and template.
844     * @param   templateName    the metadata template type name.
845     * @param   scope           the scope of the template (usually "global" or "enterprise").
846     */
847    public void deleteMetadata(String templateName, String scope) {
848        URL url = METADATA_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID(), scope, templateName);
849        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE");
850        BoxAPIResponse response = request.send();
851        response.disconnect();
852    }
853
854    /**
855     * Creates an upload session to create a new file in chunks.
856     * This will first verify that the file can be created and then open a session for uploading pieces of the file.
857     * @param fileName the name of the file to be created
858     * @param fileSize the size of the file that will be uploaded
859     * @return the created upload session instance
860     */
861    public BoxFileUploadSession.Info createUploadSession(String fileName, long fileSize) {
862
863        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL());
864        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
865
866        JsonObject body = new JsonObject();
867        body.add("folder_id", this.getID());
868        body.add("file_name", fileName);
869        body.add("file_size", fileSize);
870        request.setBody(body.toString());
871
872        BoxJSONResponse response = (BoxJSONResponse) request.send();
873        JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
874
875        String sessionId = jsonObject.get("id").asString();
876        BoxFileUploadSession session = new BoxFileUploadSession(this.getAPI(), sessionId);
877
878        return session.new Info(jsonObject);
879    }
880
881    /**
882     * Creates a new file.
883     * @param inputStream the stream instance that contains the data.
884     * @param fileName the name of the file to be created.
885     * @param fileSize the size of the file that will be uploaded.
886     * @return the created file instance.
887     * @throws InterruptedException when a thread execution is interrupted.
888     * @throws IOException when reading a stream throws exception.
889     */
890    public BoxFile.Info uploadLargeFile(InputStream inputStream, String fileName, long fileSize)
891        throws InterruptedException, IOException {
892        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL());
893        return new LargeFileUpload().
894            upload(this.getAPI(), this.getID(), inputStream, url, fileName, fileSize);
895    }
896
897    /**
898     * Creates a new file using specified number of parallel http connections.
899     * @param inputStream the stream instance that contains the data.
900     * @param fileName the name of the file to be created.
901     * @param fileSize the size of the file that will be uploaded.
902     * @param nParallelConnections number of parallel http connections to use
903     * @param timeOut time to wait before killing the job
904     * @param unit time unit for the time wait value
905     * @return the created file instance.
906     * @throws InterruptedException when a thread execution is interrupted.
907     * @throws IOException when reading a stream throws exception.
908     */
909    public BoxFile.Info uploadLargeFile(InputStream inputStream, String fileName, long fileSize,
910                                        int nParallelConnections, long timeOut, TimeUnit unit)
911        throws InterruptedException, IOException {
912        URL url = UPLOAD_SESSION_URL_TEMPLATE.build(this.getAPI().getBaseUploadURL());
913        return new LargeFileUpload(nParallelConnections, timeOut, unit).
914            upload(this.getAPI(), this.getID(), inputStream, url, fileName, fileSize);
915    }
916
917    /**
918     * Contains information about a BoxFolder.
919     */
920    public class Info extends BoxItem.Info {
921        private BoxUploadEmail uploadEmail;
922        private boolean hasCollaborations;
923        private SyncState syncState;
924        private EnumSet<Permission> permissions;
925        private boolean canNonOwnersInvite;
926        private boolean isWatermarked;
927        private Map<String, Map<String, Metadata>> metadataMap;
928
929        /**
930         * Constructs an empty Info object.
931         */
932        public Info() {
933            super();
934        }
935
936        /**
937         * Constructs an Info object by parsing information from a JSON string.
938         * @param  json the JSON string to parse.
939         */
940        public Info(String json) {
941            super(json);
942        }
943
944        /**
945         * Constructs an Info object using an already parsed JSON object.
946         * @param  jsonObject the parsed JSON object.
947         */
948        public Info(JsonObject jsonObject) {
949            super(jsonObject);
950        }
951
952        /**
953         * Gets the upload email for the folder.
954         * @return the upload email for the folder.
955         */
956        public BoxUploadEmail getUploadEmail() {
957            return this.uploadEmail;
958        }
959
960        /**
961         * Sets the upload email for the folder.
962         * @param uploadEmail the upload email for the folder.
963         */
964        public void setUploadEmail(BoxUploadEmail uploadEmail) {
965            if (this.uploadEmail == uploadEmail) {
966                return;
967            }
968
969            this.removeChildObject("folder_upload_email");
970            this.uploadEmail = uploadEmail;
971
972            if (uploadEmail == null) {
973                this.addPendingChange("folder_upload_email", (String) null);
974            } else {
975                this.addChildObject("folder_upload_email", uploadEmail);
976            }
977        }
978
979        /**
980         * Gets whether or not the folder has any collaborations.
981         * @return true if the folder has collaborations; otherwise false.
982         */
983        public boolean getHasCollaborations() {
984            return this.hasCollaborations;
985        }
986
987        /**
988         * Gets the sync state of the folder.
989         * @return the sync state of the folder.
990         */
991        public SyncState getSyncState() {
992            return this.syncState;
993        }
994
995        /**
996         * Sets the sync state of the folder.
997         * @param syncState the sync state of the folder.
998         */
999        public void setSyncState(SyncState syncState) {
1000            this.syncState = syncState;
1001            this.addPendingChange("sync_state", syncState.toJSONValue());
1002        }
1003
1004        /**
1005         * Gets the permissions that the current user has on the folder.
1006         * @return the permissions that the current user has on the folder.
1007         */
1008        public EnumSet<Permission> getPermissions() {
1009            return this.permissions;
1010        }
1011
1012        /**
1013         * Gets whether or not the non-owners can invite collaborators to the folder.
1014         * @return [description]
1015         */
1016        public boolean getCanNonOwnersInvite() {
1017            return this.canNonOwnersInvite;
1018        }
1019
1020        /**
1021         * Gets flag indicating whether this file is Watermarked.
1022         * @return whether the file is watermarked or not
1023         */
1024        public boolean getIsWatermarked() {
1025            return this.isWatermarked;
1026        }
1027
1028        /**
1029         * Gets the metadata on this folder associated with a specified scope and template.
1030         * Makes an attempt to get metadata that was retrieved using getInfo(String ...) method. If no result is found
1031         * then makes an API call to get metadata
1032         * @param   templateName    the metadata template type name.
1033         * @param   scope           the scope of the template (usually "global" or "enterprise").
1034         * @return                  the metadata returned from the server.
1035         */
1036        public Metadata getMetadata(String templateName, String scope) {
1037            try {
1038                return this.metadataMap.get(scope).get(templateName);
1039            } catch (NullPointerException e) {
1040                return null;
1041            }
1042        }
1043
1044        @Override
1045        public BoxFolder getResource() {
1046            return BoxFolder.this;
1047        }
1048
1049        @Override
1050        protected void parseJSONMember(JsonObject.Member member) {
1051            super.parseJSONMember(member);
1052
1053            String memberName = member.getName();
1054            JsonValue value = member.getValue();
1055            if (memberName.equals("folder_upload_email")) {
1056                if (this.uploadEmail == null) {
1057                    this.uploadEmail = new BoxUploadEmail(value.asObject());
1058                } else {
1059                    this.uploadEmail.update(value.asObject());
1060                }
1061
1062            } else if (memberName.equals("has_collaborations")) {
1063                this.hasCollaborations = value.asBoolean();
1064
1065            } else if (memberName.equals("sync_state")) {
1066                this.syncState = SyncState.fromJSONValue(value.asString());
1067
1068            } else if (memberName.equals("permissions")) {
1069                this.permissions = this.parsePermissions(value.asObject());
1070
1071            } else if (memberName.equals("can_non_owners_invite")) {
1072                this.canNonOwnersInvite = value.asBoolean();
1073            } else if (memberName.equals("watermark_info")) {
1074                JsonObject jsonObject = value.asObject();
1075                this.isWatermarked = jsonObject.get("is_watermarked").asBoolean();
1076            } else if (memberName.equals("metadata")) {
1077                JsonObject jsonObject = value.asObject();
1078                this.metadataMap = Parsers.parseAndPopulateMetadataMap(jsonObject);
1079            }
1080        }
1081
1082        private EnumSet<Permission> parsePermissions(JsonObject jsonObject) {
1083            EnumSet<Permission> permissions = EnumSet.noneOf(Permission.class);
1084            for (JsonObject.Member member : jsonObject) {
1085                JsonValue value = member.getValue();
1086                if (value.isNull() || !value.asBoolean()) {
1087                    continue;
1088                }
1089
1090                String memberName = member.getName();
1091                if (memberName.equals("can_download")) {
1092                    permissions.add(Permission.CAN_DOWNLOAD);
1093                } else if (memberName.equals("can_upload")) {
1094                    permissions.add(Permission.CAN_UPLOAD);
1095                } else if (memberName.equals("can_rename")) {
1096                    permissions.add(Permission.CAN_RENAME);
1097                } else if (memberName.equals("can_delete")) {
1098                    permissions.add(Permission.CAN_DELETE);
1099                } else if (memberName.equals("can_share")) {
1100                    permissions.add(Permission.CAN_SHARE);
1101                } else if (memberName.equals("can_invite_collaborator")) {
1102                    permissions.add(Permission.CAN_INVITE_COLLABORATOR);
1103                } else if (memberName.equals("can_set_share_access")) {
1104                    permissions.add(Permission.CAN_SET_SHARE_ACCESS);
1105                }
1106            }
1107
1108            return permissions;
1109        }
1110    }
1111
1112    /**
1113     * Enumerates the possible sync states that a folder can have.
1114     */
1115    public enum SyncState {
1116        /**
1117         * The folder is synced.
1118         */
1119        SYNCED("synced"),
1120
1121        /**
1122         * The folder is not synced.
1123         */
1124        NOT_SYNCED("not_synced"),
1125
1126        /**
1127         * The folder is partially synced.
1128         */
1129        PARTIALLY_SYNCED("partially_synced");
1130
1131        private final String jsonValue;
1132
1133        private SyncState(String jsonValue) {
1134            this.jsonValue = jsonValue;
1135        }
1136
1137        static SyncState fromJSONValue(String jsonValue) {
1138            return SyncState.valueOf(jsonValue.toUpperCase());
1139        }
1140
1141        String toJSONValue() {
1142            return this.jsonValue;
1143        }
1144    }
1145
1146    /**
1147     * Enumerates the possible permissions that a user can have on a folder.
1148     */
1149    public enum Permission {
1150        /**
1151         * The user can download the folder.
1152         */
1153        CAN_DOWNLOAD("can_download"),
1154
1155        /**
1156         * The user can upload to the folder.
1157         */
1158        CAN_UPLOAD("can_upload"),
1159
1160        /**
1161         * The user can rename the folder.
1162         */
1163        CAN_RENAME("can_rename"),
1164
1165        /**
1166         * The user can delete the folder.
1167         */
1168        CAN_DELETE("can_delete"),
1169
1170        /**
1171         * The user can share the folder.
1172         */
1173        CAN_SHARE("can_share"),
1174
1175        /**
1176         * The user can invite collaborators to the folder.
1177         */
1178        CAN_INVITE_COLLABORATOR("can_invite_collaborator"),
1179
1180        /**
1181         * The user can set the access level for shared links to the folder.
1182         */
1183        CAN_SET_SHARE_ACCESS("can_set_share_access");
1184
1185        private final String jsonValue;
1186
1187        private Permission(String jsonValue) {
1188            this.jsonValue = jsonValue;
1189        }
1190
1191        static Permission fromJSONValue(String jsonValue) {
1192            return Permission.valueOf(jsonValue.toUpperCase());
1193        }
1194
1195        String toJSONValue() {
1196            return this.jsonValue;
1197        }
1198    }
1199}