001package com.box.sdk;
002
003import java.io.InputStream;
004import java.net.URL;
005import java.util.ArrayList;
006import java.util.Collection;
007import java.util.Date;
008import java.util.EnumSet;
009import java.util.Iterator;
010
011import com.eclipsesource.json.JsonArray;
012import com.eclipsesource.json.JsonObject;
013import com.eclipsesource.json.JsonValue;
014
015/**
016 * Represents a folder on Box. This class can be used to iterate through a folder's contents, collaborate a folder with
017 * another user or group, and perform other common folder operations (move, copy, delete, etc.).
018 */
019public final class BoxFolder extends BoxItem implements Iterable<BoxItem.Info> {
020    /**
021     * An array of all possible folder fields that can be requested when calling {@link #getInfo()}.
022     */
023    public static final String[] ALL_FIELDS = {"type", "id", "sequence_id", "etag", "name", "created_at", "modified_at",
024        "description", "size", "path_collection", "created_by", "modified_by", "trashed_at", "purged_at",
025        "content_created_at", "content_modified_at", "owned_by", "shared_link", "folder_upload_email", "parent",
026        "item_status", "item_collection", "sync_state", "has_collaborations", "permissions", "tags",
027        "can_non_owners_invite"};
028
029    private static final String UPLOAD_FILE_URL_BASE = "https://upload.box.com/api/2.0/";
030    private static final URLTemplate CREATE_FOLDER_URL = new URLTemplate("folders");
031    private static final URLTemplate COPY_FOLDER_URL = new URLTemplate("folders/%s/copy");
032    private static final URLTemplate DELETE_FOLDER_URL = new URLTemplate("folders/%s?recursive=%b");
033    private static final URLTemplate FOLDER_INFO_URL_TEMPLATE = new URLTemplate("folders/%s");
034    private static final URLTemplate UPLOAD_FILE_URL = new URLTemplate("files/content");
035    private static final URLTemplate ADD_COLLABORATION_URL = new URLTemplate("collaborations");
036    private static final URLTemplate GET_COLLABORATIONS_URL = new URLTemplate("folders/%s/collaborations");
037    private static final URLTemplate GET_ITEMS_URL = new URLTemplate("folders/%s/items/");
038    private static final URLTemplate SEARCH_URL_TEMPLATE = new URLTemplate("search");
039
040    private final URL folderURL;
041
042    /**
043     * Constructs a BoxFolder for a folder with a given ID.
044     * @param  api the API connection to be used by the folder.
045     * @param  id  the ID of the folder.
046     */
047    public BoxFolder(BoxAPIConnection api, String id) {
048        super(api, id);
049
050        this.folderURL = FOLDER_INFO_URL_TEMPLATE.build(this.getAPI().getBaseURL(), this.getID());
051    }
052
053    /**
054     * Gets the current user's root folder.
055     * @param  api the API connection to be used by the folder.
056     * @return     the user's root folder.
057     */
058    public static BoxFolder getRootFolder(BoxAPIConnection api) {
059        return new BoxFolder(api, "0");
060    }
061
062    /**
063     * Adds a collaborator to this folder.
064     * @param  collaborator the collaborator to add.
065     * @param  role         the role of the collaborator.
066     * @return              info about the new collaboration.
067     */
068    public BoxCollaboration.Info collaborate(BoxCollaborator collaborator, BoxCollaboration.Role role) {
069        JsonObject accessibleByField = new JsonObject();
070        accessibleByField.add("id", collaborator.getID());
071
072        if (collaborator instanceof BoxUser) {
073            accessibleByField.add("type", "user");
074        } else {
075            throw new IllegalArgumentException("The given collaborator is of an unknown type.");
076        }
077
078        return this.collaborate(accessibleByField, role);
079    }
080
081    /**
082     * Adds a collaborator to this folder. An email will be sent to the collaborator if they don't already have a Box
083     * account.
084     * @param  email the email address of the collaborator to add.
085     * @param  role  the role of the collaborator.
086     * @return       info about the new collaboration.
087     */
088    public BoxCollaboration.Info collaborate(String email, BoxCollaboration.Role role) {
089        JsonObject accessibleByField = new JsonObject();
090        accessibleByField.add("login", email);
091        accessibleByField.add("type", "user");
092
093        return this.collaborate(accessibleByField, role);
094    }
095
096    private BoxCollaboration.Info collaborate(JsonObject accessibleByField, BoxCollaboration.Role role) {
097        BoxAPIConnection api = this.getAPI();
098        URL url = ADD_COLLABORATION_URL.build(api.getBaseURL());
099
100        JsonObject itemField = new JsonObject();
101        itemField.add("id", this.getID());
102        itemField.add("type", "folder");
103
104        JsonObject requestJSON = new JsonObject();
105        requestJSON.add("item", itemField);
106        requestJSON.add("accessible_by", accessibleByField);
107        requestJSON.add("role", role.toJSONString());
108
109        BoxJSONRequest request = new BoxJSONRequest(api, url, "POST");
110        request.setBody(requestJSON.toString());
111        BoxJSONResponse response = (BoxJSONResponse) request.send();
112        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
113
114        BoxCollaboration newCollaboration = new BoxCollaboration(api, responseJSON.get("id").asString());
115        BoxCollaboration.Info info = newCollaboration.new Info(responseJSON);
116        return info;
117    }
118
119    @Override
120    public BoxSharedLink createSharedLink(BoxSharedLink.Access access, Date unshareDate,
121        BoxSharedLink.Permissions permissions) {
122
123        BoxSharedLink sharedLink = new BoxSharedLink(access, unshareDate, permissions);
124        Info info = new Info();
125        info.setSharedLink(sharedLink);
126
127        this.updateInfo(info);
128        return info.getSharedLink();
129    }
130
131    /**
132     * Gets information about all of the collaborations for this folder.
133     * @return a collection of information about the collaborations for this folder.
134     */
135    public Collection<BoxCollaboration.Info> getCollaborations() {
136        BoxAPIConnection api = this.getAPI();
137        URL url = GET_COLLABORATIONS_URL.build(api.getBaseURL(), this.getID());
138
139        BoxAPIRequest request = new BoxAPIRequest(api, url, "GET");
140        BoxJSONResponse response = (BoxJSONResponse) request.send();
141        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
142
143        int entriesCount = responseJSON.get("total_count").asInt();
144        Collection<BoxCollaboration.Info> collaborations = new ArrayList<BoxCollaboration.Info>(entriesCount);
145        JsonArray entries = responseJSON.get("entries").asArray();
146        for (JsonValue entry : entries) {
147            JsonObject entryObject = entry.asObject();
148            BoxCollaboration collaboration = new BoxCollaboration(api, entryObject.get("id").asString());
149            BoxCollaboration.Info info = collaboration.new Info(entryObject);
150            collaborations.add(info);
151        }
152
153        return collaborations;
154    }
155
156    @Override
157    public BoxFolder.Info getInfo() {
158        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), this.folderURL, "GET");
159        BoxJSONResponse response = (BoxJSONResponse) request.send();
160        return new Info(response.getJSON());
161    }
162
163    @Override
164    public BoxFolder.Info getInfo(String... fields) {
165        String queryString = new QueryStringBuilder().appendParam("fields", fields).toString();
166        URL url = FOLDER_INFO_URL_TEMPLATE.buildWithQuery(this.getAPI().getBaseURL(), queryString, this.getID());
167
168        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
169        BoxJSONResponse response = (BoxJSONResponse) request.send();
170        return new Info(response.getJSON());
171    }
172
173    /**
174     * Updates the information about this folder with any info fields that have been modified locally.
175     * @param info the updated info.
176     */
177    public void updateInfo(BoxFolder.Info info) {
178        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), this.folderURL, "PUT");
179        request.setBody(info.getPendingChanges());
180        BoxJSONResponse response = (BoxJSONResponse) request.send();
181        JsonObject jsonObject = JsonObject.readFrom(response.getJSON());
182        info.update(jsonObject);
183    }
184
185    @Override
186    public BoxFolder.Info copy(BoxFolder destination) {
187        return this.copy(destination, null);
188    }
189
190    @Override
191    public BoxFolder.Info copy(BoxFolder destination, String newName) {
192        URL url = COPY_FOLDER_URL.build(this.getAPI().getBaseURL(), this.getID());
193        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "POST");
194
195        JsonObject parent = new JsonObject();
196        parent.add("id", destination.getID());
197
198        JsonObject copyInfo = new JsonObject();
199        copyInfo.add("parent", parent);
200        if (newName != null) {
201            copyInfo.add("name", newName);
202        }
203
204        request.setBody(copyInfo.toString());
205        BoxJSONResponse response = (BoxJSONResponse) request.send();
206        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
207        BoxFolder copiedFolder = new BoxFolder(this.getAPI(), responseJSON.get("id").asString());
208        return copiedFolder.new Info(responseJSON);
209    }
210
211    /**
212     * Creates a new child folder inside this folder.
213     * @param  name the new folder's name.
214     * @return      the created folder's info.
215     */
216    public BoxFolder.Info createFolder(String name) {
217        JsonObject parent = new JsonObject();
218        parent.add("id", this.getID());
219
220        JsonObject newFolder = new JsonObject();
221        newFolder.add("name", name);
222        newFolder.add("parent", parent);
223
224        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), CREATE_FOLDER_URL.build(this.getAPI().getBaseURL()),
225            "POST");
226        request.setBody(newFolder.toString());
227        BoxJSONResponse response = (BoxJSONResponse) request.send();
228        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
229
230        BoxFolder createdFolder = new BoxFolder(this.getAPI(), responseJSON.get("id").asString());
231        return createdFolder.new Info(responseJSON);
232    }
233
234    /**
235     * Deletes this folder, optionally recursively deleting all of its contents.
236     * @param recursive true to recursively delete this folder's contents; otherwise false.
237     */
238    public void delete(boolean recursive) {
239        URL url = DELETE_FOLDER_URL.build(this.getAPI().getBaseURL(), this.getID(), recursive);
240        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "DELETE");
241        BoxAPIResponse response = request.send();
242        response.disconnect();
243    }
244
245    /**
246     * Moves this folder to another folder.
247     * @param destination the destination folder.
248     */
249    public void move(BoxFolder destination) {
250        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), this.folderURL, "PUT");
251
252        JsonObject parent = new JsonObject();
253        parent.add("id", destination.getID());
254
255        JsonObject updateInfo = new JsonObject();
256        updateInfo.add("parent", parent);
257
258        request.setBody(updateInfo.toString());
259        BoxAPIResponse response = request.send();
260        response.disconnect();
261    }
262
263    /**
264     * Renames this folder.
265     * @param newName the new name of the folder.
266     */
267    public void rename(String newName) {
268        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), this.folderURL, "PUT");
269
270        JsonObject updateInfo = new JsonObject();
271        updateInfo.add("name", newName);
272
273        request.setBody(updateInfo.toString());
274        BoxAPIResponse response = request.send();
275        response.disconnect();
276    }
277
278    /**
279     * Uploads a new file to this folder.
280     * @param  fileContent a stream containing the contents of the file to upload.
281     * @param  name        the name to give the uploaded file.
282     * @return             the uploaded file's info.
283     */
284    public BoxFile.Info uploadFile(InputStream fileContent, String name) {
285        FileUploadParams uploadInfo = new FileUploadParams()
286            .setContent(fileContent)
287            .setName(name);
288        return this.uploadFile(uploadInfo);
289    }
290
291    /**
292     * Uploads a new file to this folder while reporting the progress to a ProgressListener.
293     * @param  fileContent a stream containing the contents of the file to upload.
294     * @param  name        the name to give the uploaded file.
295     * @param  fileSize    the size of the file used for determining the progress of the upload.
296     * @param  listener    a listener for monitoring the upload's progress.
297     * @return             the uploaded file's info.
298     */
299    public BoxFile.Info uploadFile(InputStream fileContent, String name, long fileSize, ProgressListener listener) {
300        FileUploadParams uploadInfo = new FileUploadParams()
301            .setContent(fileContent)
302            .setName(name)
303            .setSize(fileSize)
304            .setProgressListener(listener);
305        return this.uploadFile(uploadInfo);
306    }
307
308    /**
309     * Uploads a new file to this folder with custom upload parameters.
310     * @param  uploadParams the custom upload parameters.
311     * @return              the uploaded file's info.
312     */
313    public BoxFile.Info uploadFile(FileUploadParams uploadParams) {
314        URL uploadURL = UPLOAD_FILE_URL.build(UPLOAD_FILE_URL_BASE);
315        BoxMultipartRequest request = new BoxMultipartRequest(getAPI(), uploadURL);
316        request.putField("parent_id", getID());
317
318        if (uploadParams.getSize() > 0) {
319            request.setFile(uploadParams.getContent(), uploadParams.getName(), uploadParams.getSize());
320        } else {
321            request.setFile(uploadParams.getContent(), uploadParams.getName());
322        }
323
324        if (uploadParams.getCreated() != null) {
325            request.putField("content_created_at", uploadParams.getCreated());
326        }
327
328        if (uploadParams.getModified() != null) {
329            request.putField("content_modified_at", uploadParams.getModified());
330        }
331
332        BoxJSONResponse response;
333        if (uploadParams.getProgressListener() == null) {
334            response = (BoxJSONResponse) request.send();
335        } else {
336            response = (BoxJSONResponse) request.send(uploadParams.getProgressListener());
337        }
338        JsonObject collection = JsonObject.readFrom(response.getJSON());
339        JsonArray entries = collection.get("entries").asArray();
340        JsonObject fileInfoJSON = entries.get(0).asObject();
341        String uploadedFileID = fileInfoJSON.get("id").asString();
342
343        BoxFile uploadedFile = new BoxFile(getAPI(), uploadedFileID);
344        return uploadedFile.new Info(fileInfoJSON);
345    }
346
347    /**
348     * Returns an iterable containing the items in this folder. Iterating over the iterable returned by this method is
349     * equivalent to iterating over this BoxFolder directly.
350     * @return an iterable containing the items in this folder.
351     */
352    public Iterable<BoxItem.Info> getChildren() {
353        return this;
354    }
355
356    /**
357     * Returns an iterable containing the items in this folder and specifies which child fields to retrieve from the
358     * API.
359     * @param  fields the fields to retrieve.
360     * @return        an iterable containing the items in this folder.
361     */
362    public Iterable<BoxItem.Info> getChildren(final String... fields) {
363        return new Iterable<BoxItem.Info>() {
364            public Iterator<BoxItem.Info> iterator() {
365                String queryString = new QueryStringBuilder().appendParam("fields", fields).toString();
366                URL url = GET_ITEMS_URL.buildWithQuery(getAPI().getBaseURL(), queryString, getID());
367                return new BoxItemIterator(getAPI(), url);
368            }
369        };
370    }
371
372    /**
373     * Retrieves a specific range of child items in this folder.
374     * @param  offset the index of the first child item to retrieve.
375     * @param  limit  the maximum number of children to retrieve after the offset.
376     * @param  fields the fields to retrieve.
377     * @return        a partial collection containing the specified range of child items.
378     */
379    public PartialCollection<BoxItem.Info> getChildrenRange(long offset, long limit, String... fields) {
380        QueryStringBuilder builder = new QueryStringBuilder()
381            .appendParam("limit", limit)
382            .appendParam("offset", offset);
383
384        if (fields.length > 0) {
385            builder.appendParam("fields", fields).toString();
386        }
387
388        URL url = GET_ITEMS_URL.buildWithQuery(getAPI().getBaseURL(), builder.toString(), getID());
389        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
390        BoxJSONResponse response = (BoxJSONResponse) request.send();
391        JsonObject responseJSON = JsonObject.readFrom(response.getJSON());
392
393        String totalCountString = responseJSON.get("total_count").toString();
394        long fullSize = Double.valueOf(totalCountString).longValue();
395        PartialCollection<BoxItem.Info> children = new PartialCollection<BoxItem.Info>(offset, limit, fullSize);
396        JsonArray jsonArray = responseJSON.get("entries").asArray();
397        for (JsonValue value : jsonArray) {
398            JsonObject jsonObject = value.asObject();
399            BoxItem.Info parsedItemInfo = BoxItem.parseJSONObject(this.getAPI(), jsonObject);
400            if (parsedItemInfo != null) {
401                children.add(parsedItemInfo);
402            }
403        }
404        return children;
405    }
406
407    /**
408     * Returns an iterator over the items in this folder.
409     * @return an iterator over the items in this folder.
410     */
411    public Iterator<BoxItem.Info> iterator() {
412        URL url = GET_ITEMS_URL.build(this.getAPI().getBaseURL(), BoxFolder.this.getID());
413        return new BoxItemIterator(BoxFolder.this.getAPI(), url);
414    }
415
416    /**
417     * Searches this folder and all descendant folders using a given query.
418     * @param  query the search query.
419     * @return an Iterable containing the search results.
420     */
421    public Iterable<BoxItem.Info> search(final String query) {
422        return new Iterable<BoxItem.Info>() {
423            public Iterator<BoxItem.Info> iterator() {
424                QueryStringBuilder builder = new QueryStringBuilder();
425                builder.appendParam("query", query);
426                builder.appendParam("ancestor_folder_ids", getID());
427
428                URL url = SEARCH_URL_TEMPLATE.buildWithQuery(getAPI().getBaseURL(), builder.toString());
429                return new BoxItemIterator(getAPI(), url);
430            }
431        };
432    }
433
434    /**
435     * Contains information about a BoxFolder.
436     */
437    public class Info extends BoxItem.Info {
438        private BoxUploadEmail uploadEmail;
439        private boolean hasCollaborations;
440        private SyncState syncState;
441        private EnumSet<Permission> permissions;
442        private boolean canNonOwnersInvite;
443
444        /**
445         * Constructs an empty Info object.
446         */
447        public Info() {
448            super();
449        }
450
451        /**
452         * Constructs an Info object by parsing information from a JSON string.
453         * @param  json the JSON string to parse.
454         */
455        public Info(String json) {
456            super(json);
457        }
458
459        /**
460         * Constructs an Info object using an already parsed JSON object.
461         * @param  jsonObject the parsed JSON object.
462         */
463        Info(JsonObject jsonObject) {
464            super(jsonObject);
465        }
466
467        /**
468         * Gets the upload email for the folder.
469         * @return the upload email for the folder.
470         */
471        public BoxUploadEmail getUploadEmail() {
472            return this.uploadEmail;
473        }
474
475        /**
476         * Sets the upload email for the folder.
477         * @param uploadEmail the upload email for the folder.
478         */
479        public void setUploadEmail(BoxUploadEmail uploadEmail) {
480            if (this.uploadEmail == uploadEmail) {
481                return;
482            }
483
484            this.removeChildObject("folder_upload_email");
485            this.uploadEmail = uploadEmail;
486
487            if (uploadEmail == null) {
488                this.addPendingChange("folder_upload_email", null);
489            } else {
490                this.addChildObject("folder_upload_email", uploadEmail);
491            }
492        }
493
494        /**
495         * Gets whether or not the folder has any collaborations.
496         * @return true if the folder has collaborations; otherwise false.
497         */
498        public boolean getHasCollaborations() {
499            return this.hasCollaborations;
500        }
501
502        /**
503         * Gets the sync state of the folder.
504         * @return the sync state of the folder.
505         */
506        public SyncState getSyncState() {
507            return this.syncState;
508        }
509
510        /**
511         * Sets the sync state of the folder.
512         * @param syncState the sync state of the folder.
513         */
514        public void setSyncState(SyncState syncState) {
515            this.syncState = syncState;
516            this.addPendingChange("sync_state", syncState.toJSONValue());
517        }
518
519        /**
520         * Gets the permissions that the current user has on the folder.
521         * @return the permissions that the current user has on the folder.
522         */
523        public EnumSet<Permission> getPermissions() {
524            return this.permissions;
525        }
526
527        /**
528         * Gets whether or not the non-owners can invite collaborators to the folder.
529         * @return [description]
530         */
531        public boolean getCanNonOwnersInvite() {
532            return this.canNonOwnersInvite;
533        }
534
535        @Override
536        public BoxFolder getResource() {
537            return BoxFolder.this;
538        }
539
540        @Override
541        protected void parseJSONMember(JsonObject.Member member) {
542            super.parseJSONMember(member);
543
544            String memberName = member.getName();
545            JsonValue value = member.getValue();
546            switch (memberName) {
547                case "folder_upload_email":
548                    if (this.uploadEmail == null) {
549                        this.uploadEmail = new BoxUploadEmail(value.asObject());
550                    } else {
551                        this.uploadEmail.update(value.asObject());
552                    }
553                    break;
554                case "has_collaborations":
555                    this.hasCollaborations = value.asBoolean();
556                    break;
557                case "sync_state":
558                    this.syncState = SyncState.fromJSONValue(value.asString());
559                    break;
560                case "permissions":
561                    this.permissions = this.parsePermissions(value.asObject());
562                    break;
563                case "can_non_owners_invite":
564                    this.canNonOwnersInvite = value.asBoolean();
565                    break;
566                default:
567                    break;
568            }
569        }
570
571        private EnumSet<Permission> parsePermissions(JsonObject jsonObject) {
572            EnumSet<Permission> permissions = EnumSet.noneOf(Permission.class);
573            for (JsonObject.Member member : jsonObject) {
574                JsonValue value = member.getValue();
575                if (value.isNull() || !value.asBoolean()) {
576                    continue;
577                }
578
579                String memberName = member.getName();
580                switch (memberName) {
581                    case "can_download":
582                        permissions.add(Permission.CAN_DOWNLOAD);
583                        break;
584                    case "can_upload":
585                        permissions.add(Permission.CAN_UPLOAD);
586                        break;
587                    case "can_rename":
588                        permissions.add(Permission.CAN_RENAME);
589                        break;
590                    case "can_delete":
591                        permissions.add(Permission.CAN_DELETE);
592                        break;
593                    case "can_share":
594                        permissions.add(Permission.CAN_SHARE);
595                        break;
596                    case "can_invite_collaborator":
597                        permissions.add(Permission.CAN_INVITE_COLLABORATOR);
598                        break;
599                    case "can_set_share_access":
600                        permissions.add(Permission.CAN_SET_SHARE_ACCESS);
601                        break;
602                    default:
603                        break;
604                }
605            }
606
607            return permissions;
608        }
609    }
610
611    /**
612     * Enumerates the possible sync states that a folder can have.
613     */
614    public enum SyncState {
615        /**
616         * The folder is synced.
617         */
618        SYNCED ("synced"),
619
620        /**
621         * The folder is not synced.
622         */
623        NOT_SYNCED ("not_synced"),
624
625        /**
626         * The folder is partially synced.
627         */
628        PARTIALLY_SYNCED ("partially_synced");
629
630        private final String jsonValue;
631
632        private SyncState(String jsonValue) {
633            this.jsonValue = jsonValue;
634        }
635
636        static SyncState fromJSONValue(String jsonValue) {
637            return SyncState.valueOf(jsonValue.toUpperCase());
638        }
639
640        String toJSONValue() {
641            return this.jsonValue;
642        }
643    }
644
645    /**
646     * Enumerates the possible permissions that a user can have on a folder.
647     */
648    public enum Permission {
649        /**
650         * The user can download the folder.
651         */
652        CAN_DOWNLOAD ("can_download"),
653
654        /**
655         * The user can upload to the folder.
656         */
657        CAN_UPLOAD ("can_upload"),
658
659        /**
660         * The user can rename the folder.
661         */
662        CAN_RENAME ("can_rename"),
663
664        /**
665         * The user can delete the folder.
666         */
667        CAN_DELETE ("can_delete"),
668
669        /**
670         * The user can share the folder.
671         */
672        CAN_SHARE ("can_share"),
673
674        /**
675         * The user can invite collaborators to the folder.
676         */
677        CAN_INVITE_COLLABORATOR ("can_invite_collaborator"),
678
679        /**
680         * The user can set the access level for shared links to the folder.
681         */
682        CAN_SET_SHARE_ACCESS ("can_set_share_access");
683
684        private final String jsonValue;
685
686        private Permission(String jsonValue) {
687            this.jsonValue = jsonValue;
688        }
689
690        static Permission fromJSONValue(String jsonValue) {
691            return Permission.valueOf(jsonValue.toUpperCase());
692        }
693
694        String toJSONValue() {
695            return this.jsonValue;
696        }
697    }
698}