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        BoxJSONResponse response = (BoxJSONResponse) 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     * Returns all the sign requests.
158     *
159     * @param api    the API connection to be used by the resource.
160     * @param fields the fields to retrieve.
161     * @return an iterable with all the sign requests.
162     */
163    public static Iterable<BoxSignRequest.Info> getAll(final BoxAPIConnection api, String... fields) {
164        return getAll(api, DEFAULT_LIMIT, fields);
165    }
166
167    /**
168     * Returns all the sign requests.
169     *
170     * @param api    the API connection to be used by the resource.
171     * @param limit  the limit of items per single response. The default value is 100.
172     * @param fields the fields to retrieve.
173     * @return an iterable with all the sign requests.
174     */
175    public static Iterable<BoxSignRequest.Info> getAll(final BoxAPIConnection api, int limit, String... fields) {
176        QueryStringBuilder queryString = new QueryStringBuilder();
177        if (fields.length > 0) {
178            queryString.appendParam("fields", fields);
179        }
180        URL url = SIGN_REQUESTS_URL_TEMPLATE.buildWithQuery(api.getBaseURL(), queryString.toString());
181        return new BoxResourceIterable<BoxSignRequest.Info>(api, url, limit) {
182
183            @Override
184            protected BoxSignRequest.Info factory(JsonObject jsonObject) {
185                BoxSignRequest signRequest = new BoxSignRequest(api, jsonObject.get("id").asString());
186                return signRequest.new Info(jsonObject);
187            }
188
189        };
190    }
191
192    private static List<BoxSignRequestFile> toBoxSignRequestFiles(List<BoxFile.Info> sourceFiles) {
193        List<BoxSignRequestFile> files = new ArrayList<BoxSignRequestFile>();
194        for (BoxFile.Info sourceFile : sourceFiles) {
195            BoxSignRequestFile file = BoxSignRequestFile.fromFile(sourceFile);
196            files.add(file);
197        }
198
199        return files;
200    }
201
202    /**
203     * Returns information about this sign request.
204     *
205     * @param fields the fields to retrieve.
206     * @return information about this sign request.
207     */
208    public BoxSignRequest.Info getInfo(String... fields) {
209        QueryStringBuilder builder = new QueryStringBuilder();
210        if (fields.length > 0) {
211            builder.appendParam("fields", fields);
212        }
213        URL url = SIGN_REQUEST_URL_TEMPLATE.buildAlphaWithQuery(
214            this.getAPI().getBaseURL(), builder.toString(), this.getID());
215        BoxAPIRequest request = new BoxAPIRequest(this.getAPI(), url, "GET");
216        BoxJSONResponse response = (BoxJSONResponse) request.send();
217        JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
218        return new BoxSignRequest.Info(responseJSON);
219    }
220
221    /**
222     * Cancels a sign request if it has not yet been signed or declined.
223     * Any outstanding signers will no longer be able to sign the document.
224     *
225     * @return the cancelled sign request's info.
226     */
227    public BoxSignRequest.Info cancel() {
228        URL url = SIGN_REQUEST_CANCEL_URL_TEMPLATE.buildAlphaWithQuery(getAPI().getBaseURL(), "", this.getID());
229        BoxJSONRequest request = new BoxJSONRequest(getAPI(), url, "POST");
230        BoxJSONResponse response = (BoxJSONResponse) request.send();
231        JsonObject responseJSON = Json.parse(response.getJSON()).asObject();
232        return new BoxSignRequest.Info(responseJSON);
233    }
234
235    /**
236     * Attempts to resend a Sign Request to all signers that have not signed yet.
237     * There is a 10 minute cooling-off period between each resend request.
238     * If you make a resend call during the cooling-off period, a BoxAPIException will be thrown.
239     */
240    public void resend() {
241        URL url = SIGN_REQUEST_RESEND_URL_TEMPLATE.buildAlphaWithQuery(getAPI().getBaseURL(), "", this.getID());
242        BoxJSONRequest request = new BoxJSONRequest(getAPI(), url, "POST");
243        BoxAPIResponse response = request.send();
244        response.disconnect();
245    }
246
247    /**
248     * Represents a status of the sign request.
249     */
250    public enum BoxSignRequestStatus {
251
252        /**
253         * Converting status.
254         */
255        Converting("converting"),
256
257        /**
258         * Created status.
259         */
260        Created("created"),
261
262        /**
263         * Sent status.
264         */
265        Sent("sent"),
266
267        /**
268         * Viewed status.
269         */
270        Viewed("viewed"),
271
272        /**
273         * Signed status.
274         */
275        Signed("signed"),
276
277        /**
278         * Cancelled status.
279         */
280        Cancelled("cancelled"),
281
282        /**
283         * Declined status.
284         */
285        Declined("declined"),
286
287        /**
288         * Error converting status.
289         */
290        ErrorConverting("error_converting"),
291
292        /**
293         * Error sending status.
294         */
295        ErrorSending("error_sending"),
296
297        /**
298         * Expired status.
299         */
300        Expired("expired");
301
302        private final String jsonValue;
303
304        BoxSignRequestStatus(String jsonValue) {
305            this.jsonValue = jsonValue;
306        }
307
308        static BoxSignRequestStatus fromJSONString(String jsonValue) {
309            if ("converting".equals(jsonValue)) {
310                return Converting;
311            } else if ("created".equals(jsonValue)) {
312                return Created;
313            } else if ("sent".equals(jsonValue)) {
314                return Sent;
315            } else if ("viewed".equals(jsonValue)) {
316                return Viewed;
317            } else if ("signed".equals(jsonValue)) {
318                return Signed;
319            } else if ("cancelled".equals(jsonValue)) {
320                return Cancelled;
321            } else if ("declined".equals(jsonValue)) {
322                return Declined;
323            } else if ("error_converting".equals(jsonValue)) {
324                return ErrorConverting;
325            } else if ("error_sending".equals(jsonValue)) {
326                return ErrorSending;
327            } else if ("expired".equals(jsonValue)) {
328                return Expired;
329            }
330            throw new IllegalArgumentException("The provided JSON value isn't a valid BoxSignRequestStatus value.");
331        }
332    }
333
334    /**
335     * Contains information about the Sign Request.
336     */
337    public class Info extends BoxResource.Info {
338
339        private boolean isDocumentPreparationNeeded;
340        private boolean areTextSignaturesEnabled;
341        private boolean areDatesEnabled;
342        private BoxSignRequestSignatureColor signatureColor;
343        private String emailSubject;
344        private String emailMessage;
345        private boolean areRemindersEnabled;
346        private List<BoxFile.Info> sourceFiles;
347        private BoxFolder.Info parentFolder;
348        private List<BoxSignRequestSigner> signers;
349        private String name;
350        private List<BoxSignRequestPrefillTag> prefillTags;
351        private Integer daysValid;
352        private String externalId;
353        private String prepareUrl;
354        private BoxFile.Info signingLog;
355        private BoxSignRequestStatus status;
356        private BoxSignRequestSignFiles signFiles;
357        private Date autoExpireAt;
358
359        /**
360         * Constructs an empty Info object.
361         */
362        public Info() {
363            super();
364        }
365
366        /**
367         * Constructs an Info object by parsing information from a JSON string.
368         *
369         * @param json the JSON string to parse.
370         */
371        public Info(String json) {
372            super(json);
373        }
374
375        /**
376         * Constructs an Info object using an already parsed JSON object.
377         *
378         * @param jsonObject the parsed JSON object.
379         */
380        Info(JsonObject jsonObject) {
381            super(jsonObject);
382        }
383
384        /**
385         * Indicates if the sender should receive a prepare_url in the response to complete document preparation via UI.
386         *
387         * @return true if document preparation is needed, otherwise false.
388         */
389        public boolean getIsDocumentPreparationNeeded() {
390            return this.isDocumentPreparationNeeded;
391        }
392
393        /**
394         * Gets the flag indicating if usage of signatures generated by typing (text) is enabled.
395         *
396         * @return true if text signatures are enabled, otherwise false.
397         */
398        public boolean getAreTextSignaturesEnabled() {
399            return this.areTextSignaturesEnabled;
400        }
401
402        /**
403         * Gets the flag indicating if ability for signer to add dates is enabled.
404         *
405         * @return true if ability for signer to add dates is enabled, otherwise false.
406         */
407        public boolean getAreDatesEnabled() {
408            return this.areDatesEnabled;
409        }
410
411        /**
412         * Gets the forced, specific color for the signature.
413         *
414         * @return signature color (blue, black, red).
415         */
416        public BoxSignRequestSignatureColor getSignatureColor() {
417            return this.signatureColor;
418        }
419
420        /**
421         * Gets the subject of the sign request email.
422         *
423         * @return subject of the sign request email.
424         */
425        public String getEmailSubject() {
426            return this.emailSubject;
427        }
428
429        /**
430         * Gets the message to include in the sign request email.
431         *
432         * @return message of sign request email.
433         */
434        public String getEmailMessage() {
435            return this.emailMessage;
436        }
437
438        /**
439         * Gets the flag indicating if sending reminders for signers to sign a document on day 3, 8, 13 and 18
440         * (or less if the document has been digitally signed already) is enabled.
441         *
442         * @return true if reminders are enabled, otherwise false.
443         */
444        public boolean getAreRemindersEnabled() {
445            return this.areRemindersEnabled;
446        }
447
448        /**
449         * Gets the list of files to create a signing document from.
450         *
451         * @return list of files to create a signing document from.
452         */
453        public List<BoxFile.Info> getSourceFiles() {
454            return this.sourceFiles;
455        }
456
457        /**
458         * Gets the destination folder to place sign request specific data in (copy of source files, signing log etc.).
459         *
460         * @return destination folder to place sign request specific data in.
461         */
462        public BoxFolder.Info getParentFolder() {
463            return this.parentFolder;
464        }
465
466        /**
467         * Gets the list of signers for this sign request.
468         *
469         * @return list of signers for this sign request.
470         */
471        public List<BoxSignRequestSigner> getSigners() {
472            return this.signers;
473        }
474
475        /**
476         * Gets the name of this sign request.
477         *
478         * @return name of this sign request.
479         */
480        public String getName() {
481            return this.name;
482        }
483
484        /**
485         * Gets the list of prefill tags.
486         *
487         * @return list of prefill tags.
488         */
489        public List<BoxSignRequestPrefillTag> getPrefillTags() {
490            return this.prefillTags;
491        }
492
493        /**
494         * Gets the number of days after which this request will automatically expire if not completed.
495         *
496         * @return number of days after which this request will automatically expire if not completed.
497         */
498        public Integer getDaysValid() {
499            return this.daysValid;
500        }
501
502        /**
503         * Gets the reference id in an external system that this sign request is related to.
504         *
505         * @return external id.
506         */
507        public String getExternalId() {
508            return this.externalId;
509        }
510
511        /**
512         * Gets the URL that can be used by the sign request sender to prepare the document through the UI.
513         *
514         * @return prepare url.
515         */
516        public String getPrepareUrl() {
517            return this.prepareUrl;
518        }
519
520        /**
521         * Gets the reference to a file that will hold a log of all signer activity for this request.
522         *
523         * @return signing log.
524         */
525        public BoxFile.Info getSigningLog() {
526            return this.signingLog;
527        }
528
529        /**
530         * Gets the status of the sign request.
531         *
532         * @return sign request's status.
533         */
534        public BoxSignRequestStatus getStatus() {
535            return this.status;
536        }
537
538        /**
539         * List of files that will be signed, which are copies of the original source files.
540         * A new version of these files are created as signers sign and can be downloaded
541         * at any point in the signing process.
542         *
543         * @return sign files.
544         */
545        public BoxSignRequestSignFiles getSignFiles() {
546            return this.signFiles;
547        }
548
549        /**
550         * Uses days_valid to calculate the date and time that
551         * the sign request will expire, if unsigned.
552         *
553         * @return auto expires at date.
554         */
555        public Date getAutoExpireAt() {
556            return this.autoExpireAt;
557        }
558
559        /**
560         * {@inheritDoc}
561         */
562        @Override
563        public BoxSignRequest getResource() {
564            return BoxSignRequest.this;
565        }
566
567        /**
568         * {@inheritDoc}
569         */
570        @Override
571        void parseJSONMember(JsonObject.Member member) {
572            super.parseJSONMember(member);
573            String memberName = member.getName();
574            JsonValue value = member.getValue();
575            try {
576                if ("is_document_preparation_needed".equals(memberName)) {
577                    this.isDocumentPreparationNeeded = value.asBoolean();
578                } else if ("are_text_signatures_enabled".equals(memberName)) {
579                    this.areTextSignaturesEnabled = value.asBoolean();
580                } else if ("are_dates_enabled".equals(memberName)) {
581                    this.areDatesEnabled = value.asBoolean();
582                } else if ("signature_color".equals(memberName)) {
583                    this.signatureColor = BoxSignRequestSignatureColor.fromJSONString(value.asString());
584                } else if ("email_subject".equals(memberName)) {
585                    this.emailSubject = value.asString();
586                } else if ("email_message".equals(memberName)) {
587                    this.emailMessage = value.asString();
588                } else if ("are_reminders_enabled".equals(memberName)) {
589                    this.areRemindersEnabled = value.asBoolean();
590                } else if ("signers".equals(memberName)) {
591                    List<BoxSignRequestSigner> signers = new ArrayList<BoxSignRequestSigner>();
592                    for (JsonValue signerJSON : value.asArray()) {
593                        BoxSignRequestSigner signer = new BoxSignRequestSigner(signerJSON.asObject(), getAPI());
594                        signers.add(signer);
595                    }
596                    this.signers = signers;
597                } else if ("source_files".equals(memberName)) {
598                    this.sourceFiles = this.getFiles(value.asArray());
599                } else if ("parent_folder".equals(memberName)) {
600                    JsonObject folderJSON = value.asObject();
601                    String folderID = folderJSON.get("id").asString();
602                    BoxFolder folder = new BoxFolder(getAPI(), folderID);
603                    this.parentFolder = folder.new Info(folderJSON);
604                } else if ("name".equals(memberName)) {
605                    this.name = value.asString();
606                } else if ("prefill_tags".equals(memberName)) {
607                    List<BoxSignRequestPrefillTag> prefillTags = new ArrayList<BoxSignRequestPrefillTag>();
608                    for (JsonValue prefillTagJSON : value.asArray()) {
609                        BoxSignRequestPrefillTag prefillTag =
610                            new BoxSignRequestPrefillTag(prefillTagJSON.asObject());
611                        prefillTags.add(prefillTag);
612                    }
613                    this.prefillTags = prefillTags;
614                } else if ("days_valid".equals(memberName)) {
615                    this.daysValid = value.asInt();
616                } else if ("external_id".equals(memberName)) {
617                    this.externalId = value.asString();
618                } else if ("prepare_url".equals(memberName)) {
619                    this.prepareUrl = value.asString();
620                } else if ("signing_log".equals(memberName)) {
621                    JsonObject signingLogJSON = value.asObject();
622                    String fileID = signingLogJSON.get("id").asString();
623                    BoxFile file = new BoxFile(getAPI(), fileID);
624                    this.signingLog = file.new Info(signingLogJSON);
625                } else if ("status".equals(memberName)) {
626                    this.status = BoxSignRequestStatus.fromJSONString(value.asString());
627                } else if ("sign_files".equals(memberName)) {
628                    JsonObject signFilesJSON = value.asObject();
629                    JsonValue filesArray = signFilesJSON.get("files");
630                    List<BoxFile.Info> signFiles = this.getFiles(filesArray);
631                    boolean isReadyForDownload = signFilesJSON.get("is_ready_for_download").asBoolean();
632                    this.signFiles = new BoxSignRequestSignFiles(signFiles, isReadyForDownload);
633                } else if ("auto_expire_at".equals(memberName)) {
634                    this.autoExpireAt = BoxDateFormat.parse(value.asString());
635                }
636            } catch (Exception e) {
637                throw new BoxDeserializationException(memberName, value.toString(), e);
638            }
639        }
640
641        private List<BoxFile.Info> getFiles(JsonValue filesArray) {
642            List<BoxFile.Info> files = new ArrayList<BoxFile.Info>();
643            for (JsonValue fileJSON : filesArray.asArray()) {
644                String fileID = fileJSON.asObject().get("id").asString();
645                BoxFile file = new BoxFile(getAPI(), fileID);
646                files.add(file.new Info(fileJSON.asObject()));
647            }
648            return files;
649        }
650
651        /**
652         * List of files that will be signed, which are copies of the original source files.
653         * A new version of these files are created as signers sign and can be downloaded
654         * at any point in the signing process.
655         */
656        public class BoxSignRequestSignFiles {
657            private List<BoxFile.Info> files;
658            private boolean isReadyToDownload;
659
660            /**
661             * Constructs a BoxSignRequestSignFiles.
662             *
663             * @param files             list that signing events will occur on.
664             * @param isReadyToDownload indicating whether a change to the document is processing.
665             */
666            public BoxSignRequestSignFiles(List<BoxFile.Info> files, boolean isReadyToDownload) {
667                this.files = files;
668                this.isReadyToDownload = isReadyToDownload;
669            }
670
671            /**
672             * Gets the list of files that signing events will occur on - these are copies of the original source files.
673             *
674             * @return list of files.
675             */
676            public List<BoxFile.Info> getFiles() {
677                return this.files;
678            }
679
680            /**
681             * Gets the flag indicating whether a change to the document is processing and the PDF may be out of date.
682             * It is recommended to wait until processing has finished before downloading the PDF.
683             * Webhooks are not sent until processing has been completed.
684             *
685             * @return true if files are ready to download, otherwise false.
686             */
687            public boolean getIsReadyToDownload() {
688                return this.isReadyToDownload;
689            }
690        }
691    }
692}