001package com.box.sdk;
002
003import com.eclipsesource.json.Json;
004import com.eclipsesource.json.JsonArray;
005import com.eclipsesource.json.JsonObject;
006import com.eclipsesource.json.JsonValue;
007import java.net.URL;
008import java.util.ArrayList;
009import java.util.Date;
010import java.util.List;
011
012/**
013 * Represents a Sign Request used by Box Sign.
014 * Sign Requests are used to request e-signatures on documents from signers.
015 * A Sign Request can refer to one or more Box Files and can be sent to one or more Box Sign Request Signers.
016 *
017 * @see <a href="https://developer.box.com/reference/resources/sign-requests/">Box Sign Request</a>
018 *
019 * <p>Unless otherwise noted, the methods in this class can throw an unchecked {@link BoxAPIException} (unchecked
020 * meaning that the compiler won't force you to handle it) if an error occurs. If you wish to implement custom error
021 * handling for errors related to the Box REST API, you should capture this exception explicitly.</p>
022 */
023@BoxResourceType("sign_request")
024public class BoxSignRequest extends BoxResource {
025
026    /**
027     * The URL template used for operation Sign Request operations.
028     */
029    public static final URLTemplate SIGN_REQUESTS_URL_TEMPLATE = new URLTemplate("sign_requests");
030
031    /**
032     * The URL template used for Sign Request operations with a given ID.
033     */
034    public static final URLTemplate SIGN_REQUEST_URL_TEMPLATE = new URLTemplate("sign_requests/%s");
035
036    /**
037     * The URL template used to cancel an existing Sign Request.
038     */
039    public static final URLTemplate SIGN_REQUEST_CANCEL_URL_TEMPLATE = new URLTemplate("sign_requests/%s/cancel");
040
041    /**
042     * The URL template used to resend an existing Sign Request.
043     */
044    public static final URLTemplate SIGN_REQUEST_RESEND_URL_TEMPLATE = new URLTemplate("sign_requests/%s/resend");
045
046    /**
047     * The default limit of entries per response.
048     */
049    private static final int DEFAULT_LIMIT = 100;
050
051    /**
052     * Constructs a BoxResource for a resource with a given ID.
053     *
054     * @param api the API connection to be used by the resource.
055     * @param id  the ID of the resource.
056     */
057    public BoxSignRequest(BoxAPIConnection api, String id) {
058        super(api, id);
059    }
060
061    /**
062     * Used to create a new sign request using existing BoxFile.Info models.
063     *
064     * @param api            the API connection to be used by the created user.
065     * @param sourceFiles    the list of BoxFile.Info files to create a signing document from.
066     * @param signers        the list of signers for this sign request.
067     * @param parentFolderId the id of the destination folder to place sign request specific data in.
068     * @param optionalParams the optional parameters.
069     * @return the created sign request's info.
070     */
071    public static BoxSignRequest.Info createSignRequestFromFiles(BoxAPIConnection api,
072                                                                 List<BoxFile.Info> sourceFiles,
073                                                                 List<BoxSignRequestSigner> signers,
074                                                                 String parentFolderId,
075                                                                 BoxSignRequestCreateParams optionalParams) {
076        return createSignRequest(api, toBoxSignRequestFiles(sourceFiles), signers, parentFolderId, optionalParams);
077    }
078
079    /**
080     * Used to create a new sign request using BoxFile.Info models.
081     *
082     * @param api            the API connection to be used by the created user.
083     * @param sourceFiles    the list of BoxFile.Info files to create a signing document from.
084     * @param signers        the list of signers for this sign request.
085     * @param parentFolderId the id of the destination folder to place sign request specific data in.
086     * @return the created sign request's info.
087     */
088    public static BoxSignRequest.Info createSignRequestFromFiles(BoxAPIConnection api,
089                                                                 List<BoxFile.Info> sourceFiles,
090                                                                 List<BoxSignRequestSigner> signers,
091                                                                 String parentFolderId) {
092
093        return createSignRequest(api, toBoxSignRequestFiles(sourceFiles), signers, parentFolderId, null);
094    }
095
096    /**
097     * Used to create a new sign request.
098     *
099     * @param api            the API connection to be used by the created user.
100     * @param sourceFiles    the list of files to a signing document from.
101     * @param signers        the list of signers for this sign request.
102     * @param parentFolderId the id of the destination folder to place sign request specific data in.
103     * @return the created sign request's info.
104     */
105    public static BoxSignRequest.Info createSignRequest(BoxAPIConnection api, List<BoxSignRequestFile> sourceFiles,
106                                                        List<BoxSignRequestSigner> signers, String parentFolderId) {
107        return createSignRequest(api, sourceFiles, signers, parentFolderId, null);
108    }
109
110    /**
111     * Used to create a new sign request with optional parameters.
112     *
113     * @param api            the API connection to be used by the created user.
114     * @param signers        the list of signers for this sign request.
115     * @param sourceFiles    the list of files to a signing document from.
116     * @param parentFolderId the id of the destination folder to place sign request specific data in.
117     * @param optionalParams the optional parameters.
118     * @return the created sign request's info.
119     */
120    public static BoxSignRequest.Info createSignRequest(BoxAPIConnection api, List<BoxSignRequestFile> sourceFiles,
121                                                        List<BoxSignRequestSigner> signers, String parentFolderId,
122                                                        BoxSignRequestCreateParams optionalParams) {
123
124        JsonObject requestJSON = new JsonObject();
125
126        JsonArray sourceFilesJSON = new JsonArray();
127        for (BoxSignRequestFile sourceFile : sourceFiles) {
128            sourceFilesJSON.add(sourceFile.getJSONObject());
129        }
130        requestJSON.add("source_files", sourceFilesJSON);
131
132        JsonArray signersJSON = new JsonArray();
133        for (BoxSignRequestSigner signer : signers) {
134            signersJSON.add(signer.getJSONObject());
135        }
136        requestJSON.add("signers", signersJSON);
137
138        JsonObject parentFolderJSON = new JsonObject();
139        parentFolderJSON.add("id", parentFolderId);
140        parentFolderJSON.add("type", "folder");
141        requestJSON.add("parent_folder", parentFolderJSON);
142
143        if (optionalParams != null) {
144            optionalParams.appendParamsAsJson(requestJSON);
145        }
146
147        URL url = SIGN_REQUESTS_URL_TEMPLATE.build(api.getBaseURL());
148        BoxJSONRequest request = new BoxJSONRequest(api, url, "POST");
149        request.setBody(requestJSON.toString());
150        try (BoxJSONResponse response = request.send()) {
151            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
152            BoxSignRequest signRequest = new BoxSignRequest(api, responseJSON.get("id").asString());
153            return signRequest.new Info(responseJSON);
154        }
155    }
156
157    /**
158     * Returns all the sign requests.
159     *
160     * @param api    the API connection to be used by the resource.
161     * @param fields the fields to retrieve.
162     * @return an iterable with all the sign requests.
163     */
164    public static Iterable<BoxSignRequest.Info> getAll(final BoxAPIConnection api, String... fields) {
165        return getAll(api, DEFAULT_LIMIT, fields);
166    }
167
168    /**
169     * Returns all the sign requests.
170     *
171     * @param api    the API connection to be used by the resource.
172     * @param limit  the limit of items per single response. The default value is 100.
173     * @param fields the fields to retrieve.
174     * @return an iterable with all the sign requests.
175     */
176    public static Iterable<BoxSignRequest.Info> getAll(final BoxAPIConnection api, int limit, String... fields) {
177        QueryStringBuilder queryString = new QueryStringBuilder();
178        if (fields.length > 0) {
179            queryString.appendParam("fields", fields);
180        }
181        URL url = SIGN_REQUESTS_URL_TEMPLATE.buildWithQuery(api.getBaseURL(), queryString.toString());
182        return new BoxResourceIterable<BoxSignRequest.Info>(api, url, limit) {
183
184            @Override
185            protected BoxSignRequest.Info factory(JsonObject jsonObject) {
186                BoxSignRequest signRequest = new BoxSignRequest(api, jsonObject.get("id").asString());
187                return signRequest.new Info(jsonObject);
188            }
189
190        };
191    }
192
193    private static List<BoxSignRequestFile> toBoxSignRequestFiles(List<BoxFile.Info> sourceFiles) {
194        List<BoxSignRequestFile> files = new ArrayList<>();
195        for (BoxFile.Info sourceFile : sourceFiles) {
196            BoxSignRequestFile file = BoxSignRequestFile.fromFile(sourceFile);
197            files.add(file);
198        }
199
200        return files;
201    }
202
203    /**
204     * Returns information about this sign request.
205     *
206     * @param fields the fields to retrieve.
207     * @return information about this sign request.
208     */
209    public BoxSignRequest.Info getInfo(String... fields) {
210        QueryStringBuilder builder = new QueryStringBuilder();
211        if (fields.length > 0) {
212            builder.appendParam("fields", fields);
213        }
214        URL url = SIGN_REQUEST_URL_TEMPLATE.buildAlphaWithQuery(
215            this.getAPI().getBaseURL(), builder.toString(), this.getID());
216        BoxJSONRequest request = new BoxJSONRequest(this.getAPI(), url, "GET");
217        try (BoxJSONResponse response = request.send()) {
218            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
219            return new BoxSignRequest.Info(responseJSON);
220        }
221    }
222
223    /**
224     * Cancels a sign request if it has not yet been signed or declined.
225     * Any outstanding signers will no longer be able to sign the document.
226     *
227     * @return the cancelled sign request's info.
228     */
229    public BoxSignRequest.Info cancel() {
230        URL url = SIGN_REQUEST_CANCEL_URL_TEMPLATE.buildAlphaWithQuery(getAPI().getBaseURL(), "", this.getID());
231        BoxJSONRequest request = new BoxJSONRequest(getAPI(), url, "POST");
232        try (BoxJSONResponse response = request.send()) {
233            JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
234            return new BoxSignRequest.Info(responseJSON);
235        }
236    }
237
238    /**
239     * Attempts to resend a Sign Request to all signers that have not signed yet.
240     * There is a 10 minute cooling-off period between each resend request.
241     * If you make a resend call during the cooling-off period, a BoxAPIException will be thrown.
242     */
243    public void resend() {
244        URL url = SIGN_REQUEST_RESEND_URL_TEMPLATE.buildAlphaWithQuery(getAPI().getBaseURL(), "", this.getID());
245        BoxAPIRequest request = new BoxAPIRequest(getAPI(), url, "POST");
246        request.send().close();
247    }
248
249    /**
250     * Represents a status of the sign request.
251     */
252    public enum BoxSignRequestStatus {
253
254        /**
255         * Converting status.
256         */
257        Converting("converting"),
258
259        /**
260         * Created status.
261         */
262        Created("created"),
263
264        /**
265         * Sent status.
266         */
267        Sent("sent"),
268
269        /**
270         * Viewed status.
271         */
272        Viewed("viewed"),
273
274        /**
275         * Signed status.
276         */
277        Signed("signed"),
278
279        /**
280         * Cancelled status.
281         */
282        Cancelled("cancelled"),
283
284        /**
285         * Declined status.
286         */
287        Declined("declined"),
288
289        /**
290         * Error converting status.
291         */
292        ErrorConverting("error_converting"),
293
294        /**
295         * Error sending status.
296         */
297        ErrorSending("error_sending"),
298
299        /**
300         * Expired status.
301         */
302        Expired("expired");
303
304        private final String jsonValue;
305
306        BoxSignRequestStatus(String jsonValue) {
307            this.jsonValue = jsonValue;
308        }
309
310        static BoxSignRequestStatus fromJSONString(String jsonValue) {
311            if ("converting".equals(jsonValue)) {
312                return Converting;
313            } else if ("created".equals(jsonValue)) {
314                return Created;
315            } else if ("sent".equals(jsonValue)) {
316                return Sent;
317            } else if ("viewed".equals(jsonValue)) {
318                return Viewed;
319            } else if ("signed".equals(jsonValue)) {
320                return Signed;
321            } else if ("cancelled".equals(jsonValue)) {
322                return Cancelled;
323            } else if ("declined".equals(jsonValue)) {
324                return Declined;
325            } else if ("error_converting".equals(jsonValue)) {
326                return ErrorConverting;
327            } else if ("error_sending".equals(jsonValue)) {
328                return ErrorSending;
329            } else if ("expired".equals(jsonValue)) {
330                return Expired;
331            }
332            throw new IllegalArgumentException("The provided JSON value isn't a valid BoxSignRequestStatus value.");
333        }
334    }
335
336    /**
337     * Contains information about the Sign Request.
338     */
339    public class Info extends BoxResource.Info {
340
341        private boolean isDocumentPreparationNeeded;
342        private boolean areTextSignaturesEnabled;
343        private boolean areDatesEnabled;
344        private BoxSignRequestSignatureColor signatureColor;
345        private String emailSubject;
346        private String emailMessage;
347        private boolean areRemindersEnabled;
348        private List<BoxFile.Info> sourceFiles;
349        private BoxFolder.Info parentFolder;
350        private List<BoxSignRequestSigner> signers;
351        private String name;
352        private List<BoxSignRequestPrefillTag> prefillTags;
353        private Integer daysValid;
354        private String externalId;
355        private String prepareUrl;
356        private BoxFile.Info signingLog;
357        private BoxSignRequestStatus status;
358        private BoxSignRequestSignFiles signFiles;
359        private Date autoExpireAt;
360        private String redirectUrl;
361        private String declinedRedirectUrl;
362
363        /**
364         * Constructs an empty Info object.
365         */
366        public Info() {
367            super();
368        }
369
370        /**
371         * Constructs an Info object by parsing information from a JSON string.
372         *
373         * @param json the JSON string to parse.
374         */
375        public Info(String json) {
376            super(json);
377        }
378
379        /**
380         * Constructs an Info object using an already parsed JSON object.
381         *
382         * @param jsonObject the parsed JSON object.
383         */
384        Info(JsonObject jsonObject) {
385            super(jsonObject);
386        }
387
388        /**
389         * Indicates if the sender should receive a prepare_url in the response to complete document preparation via UI.
390         *
391         * @return true if document preparation is needed, otherwise false.
392         */
393        public boolean getIsDocumentPreparationNeeded() {
394            return this.isDocumentPreparationNeeded;
395        }
396
397        /**
398         * Gets the flag indicating if usage of signatures generated by typing (text) is enabled.
399         *
400         * @return true if text signatures are enabled, otherwise false.
401         */
402        public boolean getAreTextSignaturesEnabled() {
403            return this.areTextSignaturesEnabled;
404        }
405
406        /**
407         * Gets the flag indicating if ability for signer to add dates is enabled.
408         *
409         * @return true if ability for signer to add dates is enabled, otherwise false.
410         */
411        public boolean getAreDatesEnabled() {
412            return this.areDatesEnabled;
413        }
414
415        /**
416         * Gets the forced, specific color for the signature.
417         *
418         * @return signature color (blue, black, red).
419         */
420        public BoxSignRequestSignatureColor getSignatureColor() {
421            return this.signatureColor;
422        }
423
424        /**
425         * Gets the subject of the sign request email.
426         *
427         * @return subject of the sign request email.
428         */
429        public String getEmailSubject() {
430            return this.emailSubject;
431        }
432
433        /**
434         * Gets the message to include in the sign request email.
435         *
436         * @return message of sign request email.
437         */
438        public String getEmailMessage() {
439            return this.emailMessage;
440        }
441
442        /**
443         * Gets the flag indicating if sending reminders for signers to sign a document on day 3, 8, 13 and 18
444         * (or less if the document has been digitally signed already) is enabled.
445         *
446         * @return true if reminders are enabled, otherwise false.
447         */
448        public boolean getAreRemindersEnabled() {
449            return this.areRemindersEnabled;
450        }
451
452        /**
453         * Gets the list of files to create a signing document from.
454         *
455         * @return list of files to create a signing document from.
456         */
457        public List<BoxFile.Info> getSourceFiles() {
458            return this.sourceFiles;
459        }
460
461        /**
462         * Gets the destination folder to place sign request specific data in (copy of source files, signing log etc.).
463         *
464         * @return destination folder to place sign request specific data in.
465         */
466        public BoxFolder.Info getParentFolder() {
467            return this.parentFolder;
468        }
469
470        /**
471         * Gets the list of signers for this sign request.
472         *
473         * @return list of signers for this sign request.
474         */
475        public List<BoxSignRequestSigner> getSigners() {
476            return this.signers;
477        }
478
479        /**
480         * Gets the name of this sign request.
481         *
482         * @return name of this sign request.
483         */
484        public String getName() {
485            return this.name;
486        }
487
488        /**
489         * Gets the list of prefill tags.
490         *
491         * @return list of prefill tags.
492         */
493        public List<BoxSignRequestPrefillTag> getPrefillTags() {
494            return this.prefillTags;
495        }
496
497        /**
498         * Gets the number of days after which this request will automatically expire if not completed.
499         *
500         * @return number of days after which this request will automatically expire if not completed.
501         */
502        public Integer getDaysValid() {
503            return this.daysValid;
504        }
505
506        /**
507         * Gets the reference id in an external system that this sign request is related to.
508         *
509         * @return external id.
510         */
511        public String getExternalId() {
512            return this.externalId;
513        }
514
515        /**
516         * Gets the URL that can be used by the sign request sender to prepare the document through the UI.
517         *
518         * @return prepare url.
519         */
520        public String getPrepareUrl() {
521            return this.prepareUrl;
522        }
523
524        /**
525         * Gets the reference to a file that will hold a log of all signer activity for this request.
526         *
527         * @return signing log.
528         */
529        public BoxFile.Info getSigningLog() {
530            return this.signingLog;
531        }
532
533        /**
534         * Gets the status of the sign request.
535         *
536         * @return sign request's status.
537         */
538        public BoxSignRequestStatus getStatus() {
539            return this.status;
540        }
541
542        /**
543         * List of files that will be signed, which are copies of the original source files.
544         * A new version of these files are created as signers sign and can be downloaded
545         * at any point in the signing process.
546         *
547         * @return sign files.
548         */
549        public BoxSignRequestSignFiles getSignFiles() {
550            return this.signFiles;
551        }
552
553        /**
554         * Uses days_valid to calculate the date and time that
555         * the sign request will expire, if unsigned.
556         *
557         * @return auto expires at date.
558         */
559        public Date getAutoExpireAt() {
560            return this.autoExpireAt;
561        }
562
563        /**
564         * Gets the URL that will be redirected to after the signer completes the sign request.
565         *
566         * @return redirect url.
567         */
568        public String getRedirectUrl() {
569            return this.redirectUrl;
570        }
571
572        /**
573         * Gets the URL that will be redirected to after the signer declines the sign request.
574         *
575         * @return declined redirect url.
576         */
577        public String getDeclinedRedirectUrl() {
578            return this.declinedRedirectUrl;
579        }
580
581        /**
582         * {@inheritDoc}
583         */
584        @Override
585        public BoxSignRequest getResource() {
586            return BoxSignRequest.this;
587        }
588
589        /**
590         * {@inheritDoc}
591         */
592        @Override
593        void parseJSONMember(JsonObject.Member member) {
594            super.parseJSONMember(member);
595            String memberName = member.getName();
596            JsonValue value = member.getValue();
597            try {
598                switch (memberName) {
599                    case "is_document_preparation_needed":
600                        this.isDocumentPreparationNeeded = value.asBoolean();
601                        break;
602                    case "are_text_signatures_enabled":
603                        this.areTextSignaturesEnabled = value.asBoolean();
604                        break;
605                    case "are_dates_enabled":
606                        this.areDatesEnabled = value.asBoolean();
607                        break;
608                    case "signature_color":
609                        this.signatureColor = BoxSignRequestSignatureColor.fromJSONString(value.asString());
610                        break;
611                    case "email_subject":
612                        this.emailSubject = value.asString();
613                        break;
614                    case "email_message":
615                        this.emailMessage = value.asString();
616                        break;
617                    case "are_reminders_enabled":
618                        this.areRemindersEnabled = value.asBoolean();
619                        break;
620                    case "signers":
621                        List<BoxSignRequestSigner> signers = new ArrayList<>();
622                        for (JsonValue signerJSON : value.asArray()) {
623                            BoxSignRequestSigner signer = new BoxSignRequestSigner(signerJSON.asObject(), getAPI());
624                            signers.add(signer);
625                        }
626                        this.signers = signers;
627                        break;
628                    case "source_files":
629                        this.sourceFiles = this.getFiles(value.asArray());
630                        break;
631                    case "parent_folder":
632                        JsonObject folderJSON = value.asObject();
633                        String folderID = folderJSON.get("id").asString();
634                        BoxFolder folder = new BoxFolder(getAPI(), folderID);
635                        this.parentFolder = folder.new Info(folderJSON);
636                        break;
637                    case "name":
638                        this.name = value.asString();
639                        break;
640                    case "prefill_tags":
641                        List<BoxSignRequestPrefillTag> prefillTags = new ArrayList<>();
642                        for (JsonValue prefillTagJSON : value.asArray()) {
643                            BoxSignRequestPrefillTag prefillTag =
644                                new BoxSignRequestPrefillTag(prefillTagJSON.asObject());
645                            prefillTags.add(prefillTag);
646                        }
647                        this.prefillTags = prefillTags;
648                        break;
649                    case "days_valid":
650                        this.daysValid = value.asInt();
651                        break;
652                    case "external_id":
653                        this.externalId = value.asString();
654                        break;
655                    case "prepare_url":
656                        this.prepareUrl = value.asString();
657                        break;
658                    case "signing_log":
659                        JsonObject signingLogJSON = value.asObject();
660                        String fileID = signingLogJSON.get("id").asString();
661                        BoxFile file = new BoxFile(getAPI(), fileID);
662                        this.signingLog = file.new Info(signingLogJSON);
663                        break;
664                    case "status":
665                        this.status = BoxSignRequestStatus.fromJSONString(value.asString());
666                        break;
667                    case "sign_files":
668                        JsonObject signFilesJSON = value.asObject();
669                        JsonValue filesArray = signFilesJSON.get("files");
670                        List<BoxFile.Info> signFiles = this.getFiles(filesArray);
671                        boolean isReadyForDownload = signFilesJSON.get("is_ready_for_download").asBoolean();
672                        this.signFiles = new BoxSignRequestSignFiles(signFiles, isReadyForDownload);
673                        break;
674                    case "auto_expire_at":
675                        this.autoExpireAt = BoxDateFormat.parse(value.asString());
676                        break;
677                    case "redirect_url":
678                        this.redirectUrl = value.asString();
679                        break;
680                    case "declined_redirect_url":
681                        this.declinedRedirectUrl = value.asString();
682                        break;
683                    default:
684                }
685            } catch (Exception e) {
686                throw new BoxDeserializationException(memberName, value.toString(), e);
687            }
688        }
689
690        private List<BoxFile.Info> getFiles(JsonValue filesArray) {
691            List<BoxFile.Info> files = new ArrayList<>();
692            for (JsonValue fileJSON : filesArray.asArray()) {
693                String fileID = fileJSON.asObject().get("id").asString();
694                BoxFile file = new BoxFile(getAPI(), fileID);
695                files.add(file.new Info(fileJSON.asObject()));
696            }
697            return files;
698        }
699
700        /**
701         * List of files that will be signed, which are copies of the original source files.
702         * A new version of these files are created as signers sign and can be downloaded
703         * at any point in the signing process.
704         */
705        public class BoxSignRequestSignFiles {
706            private final List<BoxFile.Info> files;
707            private final boolean isReadyToDownload;
708
709            /**
710             * Constructs a BoxSignRequestSignFiles.
711             *
712             * @param files             list that signing events will occur on.
713             * @param isReadyToDownload indicating whether a change to the document is processing.
714             */
715            public BoxSignRequestSignFiles(List<BoxFile.Info> files, boolean isReadyToDownload) {
716                this.files = files;
717                this.isReadyToDownload = isReadyToDownload;
718            }
719
720            /**
721             * Gets the list of files that signing events will occur on - these are copies of the original source files.
722             *
723             * @return list of files.
724             */
725            public List<BoxFile.Info> getFiles() {
726                return this.files;
727            }
728
729            /**
730             * Gets the flag indicating whether a change to the document is processing and the PDF may be out of date.
731             * It is recommended to wait until processing has finished before downloading the PDF.
732             * Webhooks are not sent until processing has been completed.
733             *
734             * @return true if files are ready to download, otherwise false.
735             */
736            public boolean getIsReadyToDownload() {
737                return this.isReadyToDownload;
738            }
739        }
740    }
741}