├── docs ├── step1.png ├── step2.png ├── step3.png ├── step4.png ├── step5.png ├── step6.png ├── step8.png ├── RunAndDebug.png ├── connect_listener_architecture.png └── Readme.Eclipse.md ├── .settings ├── org.eclipse.m2e.core.prefs ├── org.eclipse.core.resources.prefs └── org.eclipse.jdt.core.prefs ├── .gitignore ├── src ├── main │ ├── resources │ │ ├── World_Wide_Corp_lorem.pdf │ │ ├── World_Wide_Corp_Battle_Plan_Trafalgar.docx │ │ └── config.properties │ └── java │ │ └── com │ │ └── docusign │ │ └── example │ │ └── worker │ │ ├── DatePretty.java │ │ ├── JWTAuth.java │ │ ├── DSConfig.java │ │ ├── AWSWorker.java │ │ └── ProcessNotification.java ├── test │ └── java │ │ └── com │ │ └── docusign │ │ └── example │ │ └── test │ │ ├── ManyTesting.java │ │ ├── FewTesting.java │ │ ├── DSHelper.java │ │ ├── SavingEnvTest.java │ │ ├── RunTest.java │ │ └── CreateEnvelope.java └── LICENSE ├── .project ├── LICENSE ├── .classpath ├── pom.xml └── README.md /docs/step1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/connect-java-worker-aws/master/docs/step1.png -------------------------------------------------------------------------------- /docs/step2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/connect-java-worker-aws/master/docs/step2.png -------------------------------------------------------------------------------- /docs/step3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/connect-java-worker-aws/master/docs/step3.png -------------------------------------------------------------------------------- /docs/step4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/connect-java-worker-aws/master/docs/step4.png -------------------------------------------------------------------------------- /docs/step5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/connect-java-worker-aws/master/docs/step5.png -------------------------------------------------------------------------------- /docs/step6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/connect-java-worker-aws/master/docs/step6.png -------------------------------------------------------------------------------- /docs/step8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/connect-java-worker-aws/master/docs/step8.png -------------------------------------------------------------------------------- /docs/RunAndDebug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/connect-java-worker-aws/master/docs/RunAndDebug.png -------------------------------------------------------------------------------- /.settings/org.eclipse.m2e.core.prefs: -------------------------------------------------------------------------------- 1 | activeProfiles= 2 | eclipse.preferences.version=1 3 | resolveWorkspaceProjects=true 4 | version=1 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .DS_Store 3 | output/ 4 | test_messages/ 5 | target/ 6 | 7 | *.iml 8 | 9 | downloaded_documents/ 10 | 11 | -------------------------------------------------------------------------------- /docs/connect_listener_architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/connect-java-worker-aws/master/docs/connect_listener_architecture.png -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding//src/test/java/com/docusign/example/test/CreateEnvelope.java=UTF-8 3 | -------------------------------------------------------------------------------- /src/main/resources/World_Wide_Corp_lorem.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/connect-java-worker-aws/master/src/main/resources/World_Wide_Corp_lorem.pdf -------------------------------------------------------------------------------- /src/main/resources/World_Wide_Corp_Battle_Plan_Trafalgar.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/docusign/connect-java-worker-aws/master/src/main/resources/World_Wide_Corp_Battle_Plan_Trafalgar.docx -------------------------------------------------------------------------------- /src/test/java/com/docusign/example/test/ManyTesting.java: -------------------------------------------------------------------------------- 1 | package com.docusign.example.test; 2 | 3 | import org.junit.Test; 4 | 5 | public class ManyTesting { 6 | 7 | @Test 8 | public void test() throws InterruptedException { 9 | // Sending many tests every hour for 8 hours 10 | RunTest test = new RunTest(); 11 | test.startTest("many"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/com/docusign/example/test/FewTesting.java: -------------------------------------------------------------------------------- 1 | package com.docusign.example.test; 2 | import static org.junit.Assert.*; 3 | import org.junit.Test; 4 | 5 | public class FewTesting { 6 | 7 | @Test 8 | public void test() throws InterruptedException { 9 | // Sending 5 tests every hour for 8 hours 10 | RunTest test = new RunTest(); 11 | test.startTest("few"); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /.settings/org.eclipse.jdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7 3 | org.eclipse.jdt.core.compiler.compliance=1.7 4 | org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled 5 | org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning 6 | org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore 7 | org.eclipse.jdt.core.compiler.release=disabled 8 | org.eclipse.jdt.core.compiler.source=1.7 9 | -------------------------------------------------------------------------------- /src/main/java/com/docusign/example/worker/DatePretty.java: -------------------------------------------------------------------------------- 1 | package com.docusign.example.worker; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Date; 5 | 6 | public abstract class DatePretty { 7 | 8 | /** 9 | * Prints pretty the current day and time 10 | * @return string of date and time 11 | */ 12 | public static String date() { 13 | Date date = new Date(); 14 | SimpleDateFormat formatter = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss"); 15 | return formatter.format(date)+" "; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | connect-java-worker-aws 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | org.eclipse.m2e.core.maven2Builder 15 | 16 | 17 | 18 | 19 | 20 | org.eclipse.jdt.core.javanature 21 | org.eclipse.m2e.core.maven2Nature 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/test/java/com/docusign/example/test/DSHelper.java: -------------------------------------------------------------------------------- 1 | package com.docusign.example.test; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.fasterxml.jackson.databind.SerializationFeature; 5 | import java.io.*; 6 | 7 | public class DSHelper { 8 | private static final ObjectMapper mapper = new ObjectMapper().enable(SerializationFeature.INDENT_OUTPUT); 9 | 10 | /** 11 | * This method read bytes content from resource 12 | * 13 | * @param path - resource path 14 | * @return - return bytes array 15 | * @throws IOException 16 | */ 17 | 18 | public static byte[] readContent(String path) throws IOException { 19 | 20 | InputStream is = DSHelper.class.getResourceAsStream("/"+path); 21 | ByteArrayOutputStream buffer = new ByteArrayOutputStream(); 22 | int nRead; 23 | byte[] data = new byte[1024]; 24 | while ((nRead = is.read(data, 0, data.length)) != -1) { 25 | buffer.write(data, 0, nRead); 26 | } 27 | buffer.flush(); 28 | return buffer.toByteArray(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2019 DocuSign, Inc. (https://www.docusign.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2018 DocuSign, Inc. (https://www.docusign.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.docusign.examples.worker 8 | connect-java-worker-aws 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 13 | org.apache.maven.plugins 14 | maven-compiler-plugin 15 | 16 | 7 17 | 7 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | com.docusign 26 | docusign-esign-java 27 | 3.2.0-RC2 28 | 29 | 30 | com.amazonaws 31 | aws-java-sdk-bom 32 | 1.11.106 33 | pom 34 | import 35 | 36 | 37 | org.apache.directory.studio 38 | org.apache.commons.io 39 | 2.4 40 | 41 | 42 | com.amazonaws 43 | aws-java-sdk-sqs 44 | 1.11.595 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/test/java/com/docusign/example/test/SavingEnvTest.java: -------------------------------------------------------------------------------- 1 | package com.docusign.example.test; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.util.concurrent.TimeUnit; 6 | 7 | import org.junit.Assert; 8 | import org.junit.Test; 9 | import com.docusign.esign.client.ApiClient; 10 | import com.docusign.esign.client.ApiException; 11 | import com.docusign.esign.model.EnvelopeSummary; 12 | import com.docusign.example.worker.DSConfig; 13 | import com.docusign.example.worker.ProcessNotification; 14 | import com.docusign.example.test.CreateEnvelope; 15 | import com.docusign.example.worker.DatePretty; 16 | 17 | public class SavingEnvTest { 18 | 19 | private static final ApiClient apiClient = new ApiClient(); 20 | 21 | @Test 22 | public void test() { 23 | System.out.println(DatePretty.date() + "Starting"); 24 | sendEnvelope(); 25 | created(); 26 | System.out.println(DatePretty.date() + "Done"); 27 | } 28 | 29 | public void sendEnvelope() { 30 | try { 31 | System.setProperty("https.protocols","TLSv1.2"); 32 | System.out.println("Sending an envelope. The envelope includes HTML, Word, and PDF documents.\n" 33 | + "It takes about 15 seconds for DocuSign to process the envelope request... "); 34 | EnvelopeSummary result = new CreateEnvelope(apiClient).sendEnvelope(); 35 | System.out.println( 36 | String.format("Envelope status: %s. Envelope ID: %s", 37 | result.getStatus(), 38 | result.getEnvelopeId())); 39 | 40 | } 41 | catch (IOException e) { 42 | e.printStackTrace(); 43 | } 44 | catch (ApiException e) { 45 | System.err.println("\nDocuSign Exception!"); 46 | } 47 | } 48 | 49 | /** 50 | * Checks if the program created file from the envelope 51 | */ 52 | private void created() { 53 | try { 54 | TimeUnit.SECONDS.sleep(30); 55 | File file = new File(ProcessNotification.mainPath + "\\output\\order_Test_Mode.pdf"); 56 | if(!file.exists()) { 57 | Assert.fail("Failed to find the file"); 58 | } 59 | } 60 | catch (InterruptedException e) { 61 | Assert.fail("Failed to sleep: " + e.getMessage()); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/resources/config.properties: -------------------------------------------------------------------------------- 1 | # Do not store secret information in your code repository 2 | # 3 | # For the following settings do not use quotes. 4 | # Integrator Key (client id) 5 | DS_CLIENT_ID={CLIENT_ID} 6 | # API username Guid 7 | DS_IMPERSONATED_USER_GUID={USER_ID} 8 | DS_TARGET_ACCOUNT_ID=FALSE 9 | # authentication server host name for production: account.docusign.com 10 | DS_AUTH_SERVER=account-d.docusign.com 11 | # The required Basic Auth Name (From Connect) 12 | BASIC_AUTH_NAME={AUTH_NAME} 13 | # The required Basic Auth Password (From Connect) 14 | BASIC_AUTH_PW={AUTH_PW} 15 | # QUEUE_URL 16 | QUEUE_URL={QUEUE_URL} 17 | # QUEUE_REGION 18 | QUEUE_REGION={QUEUE_REGION} 19 | # Send debugging statements to console 20 | DEBUG=true 21 | SALES_ORDER={SALES_ORDER} 22 | # accessKeyId 23 | AWS_ACCOUNT={AWS_ACCOUNT} 24 | # secretAccessKey 25 | AWS_SECRET={AWS_SECRET} 26 | # should the worker break tests be enabled? Disable to clear the queue 27 | ENABLE_BREAK_TEST=true 28 | # The value of this field is used in the output file name 29 | ENVELOPE_CUSTOM_FIELD=Sales order 30 | OUTPUT_FILE_PREFIX=order_ 31 | # 32 | # For the private key, use the following format, but without 33 | # the hash at the beginning of the lines. 34 | # private key string 35 | DS_PRIVATE_KEY={RSA_PRIVATE_KEY} 36 | # DS_PRIVATE_KEY=\n\ 37 | #-----BEGIN RSA PRIVATE KEY-----\n\ 38 | #MIIEpAIBAAKCAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n\ 39 | #MIIEpAIBAAKCAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n\ 40 | #MIIEpAIBAAKCAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n\ 41 | # ... 42 | #UC1WqwKBgQCY/6aZxlWX9XYgsQsnUjhj2aTwr7pCiQuYceIzLTQzy+nz8M4PfCE1\n\ 43 | #PEHgznlGh/vUboCuA4tQOcKytxFfKG4F+jM/g4GH9z46KZOow3Hb6g==\n\ 44 | #-----END RSA PRIVATE KEY-----\n\ 45 | 46 | # These settings are only needed for using the tests/test.js file 47 | # URL for enquing a test. Same as the listener's url 48 | ENQUEUE_URL={ENQUEUE_URL} 49 | DS_SIGNER_1_EMAIL={DS_SIGNER_1_EMAIL} 50 | # Signer name 51 | DS_SIGNER_1_NAME={DS_SIGNER_1_NAME} 52 | # Carbon copy email address 53 | DS_CC_1_EMAIL={DS_CC_1_EMAIL} 54 | # Carbon copy name 55 | DS_CC_1_NAME={DS_CC_1_NAME} 56 | TEST_OUTPUT_DIR_NAME=test_messages -------------------------------------------------------------------------------- /docs/Readme.Eclipse.md: -------------------------------------------------------------------------------- 1 | # Eclipse installation 2 | 3 | The [IntelliJ IDE](https://www.jetbrains.com/idea/) 4 | (community or ultimate editions) can also be 5 | used with this example. 6 | 7 | **Step 1.** Download or clone the 8 | [connect-java-worker-aws](https://github.com/docusign/connect-java-worker-aws) 9 | repository 10 | 11 | **Step 2.** Start Eclipse click on **File** at the top left corner 12 | and choose the **Import** option. 13 | 14 | ![Start Eclipse](step2.png) 15 | 16 | **Step 3.** Click on the **Maven** directory and choose the **Existing Maven Projects** option. 17 | Then Click **Next >** 18 | 19 | ![Import Maven project](step3.png) 20 | 21 | **Step 4.** Click on **Browse** and search for the repository 22 | 23 | ![Brows for file](step4.png) 24 | 25 | **Step 5.** Once you have found the repository, choose it and click on **Select Folder** 26 | 27 | ![Select the file](step5.png) 28 | 29 | **Step 6.** Make sure you chose the right repository 30 | and mark the **/pom.xml** if its not marked yet. 31 | 32 | **Step 7.** Click **Finish**. 33 | 34 | ![Finish the Import](step6.png) 35 | 36 | **Step 8.** The project will be displayed in the **Project Explorer**. 37 | 38 | ![Project Explorer](step8.png) 39 | 40 | ## Configuring the project 41 | Configure the example as discussed in the repository's Readme. 42 | 43 | You can use the IDE to edit the 44 | `/src/main/resources/config.properties` file. 45 | 46 | ## Running or debugging the example 47 | 48 | The Main class of this project is **AWSWorker.java**. 49 | 50 | The Eclipse toolbar contains icons for Running and for debugging the example: 51 | 52 | The left insect-shaped icon is used for Debugging and the right round icon is used for Run the example. 53 | 54 | In order for them to work, you **have** to use them from the **AWSWorker.java** class. 55 | 56 | ![Running or debugging the example](RunAndDebug.png) 57 | 58 | ## Configuring the example via environment variables 59 | Instead of the config.properties file, environment variables 60 | can be used. 61 | 62 | The IntelliJ's **Run Configuration** screen can be used to 63 | set the environment variables. Unfortunately, the IDE 64 | [does not support multi-line environment variables](https://youtrack.jetbrains.com/issue/IDEA-185315). 65 | 66 | As a work-around, the private key can be entered into 67 | the IDE as a single line. Before doing so, append `\n\` 68 | to each line of the private key. 69 | -------------------------------------------------------------------------------- /src/main/java/com/docusign/example/worker/JWTAuth.java: -------------------------------------------------------------------------------- 1 | package com.docusign.example.worker; 2 | 3 | import com.docusign.esign.client.*; 4 | import com.docusign.esign.client.auth.OAuth; 5 | import java.io.File; 6 | import java.io.IOException; 7 | import java.util.List; 8 | import java.util.ArrayList; 9 | 10 | /** 11 | * This is an example base class to be extended to show functionality example. 12 | * its has a apiClient member as a constructor argument for later usage in API calls. 13 | */ 14 | public class JWTAuth { 15 | 16 | private static final long TOKEN_EXPIRATION_IN_SECONDS = 3600; 17 | private static final long TOKEN_REPLACEMENT_IN_MILLISECONDS = 10 * 60 * 1000; 18 | 19 | private static OAuth.Account _account; 20 | private static File privateKeyTempFile = null; 21 | private static long expiresIn; 22 | private static String basePath; 23 | private static String _token = null; 24 | protected final ApiClient apiClient; 25 | 26 | protected String getAccountId() { 27 | return _account.getAccountId(); 28 | }; 29 | 30 | protected String getToken() { 31 | return this._token; 32 | } 33 | 34 | protected String getBasePath() { 35 | return this.basePath; 36 | } 37 | 38 | 39 | public JWTAuth(ApiClient apiClient) throws IOException { 40 | this.apiClient = apiClient; 41 | } 42 | 43 | public void checkToken() throws IOException, ApiException { 44 | if(this._token == null || (System.currentTimeMillis() + TOKEN_REPLACEMENT_IN_MILLISECONDS) > this.expiresIn) { 45 | updateToken(); 46 | } 47 | } 48 | 49 | private void updateToken() throws IOException, ApiException { 50 | System.out.println("Fetching an access token via JWT grant..."); 51 | 52 | java.util.List scopes = new ArrayList(); 53 | // Only signature scope is needed. Impersonation scope is implied. 54 | scopes.add(OAuth.Scope_SIGNATURE); 55 | String privateKey = DSConfig.PRIVATE_KEY.replace("\\n","\n"); 56 | byte[] privateKeyBytes = privateKey.getBytes(); 57 | apiClient.setOAuthBasePath(DSConfig.DS_AUTH_SERVER); 58 | 59 | OAuth.OAuthToken oAuthToken = apiClient.requestJWTUserToken ( 60 | DSConfig.CLIENT_ID, 61 | DSConfig.IMPERSONATED_USER_GUID, 62 | scopes, 63 | privateKeyBytes, 64 | TOKEN_EXPIRATION_IN_SECONDS); 65 | apiClient.setAccessToken(oAuthToken.getAccessToken(), oAuthToken.getExpiresIn()); 66 | System.out.println("Done. Continuing..."); 67 | 68 | if(_account == null) 69 | _account = this.getAccountInfo(apiClient); 70 | // default or configured account id. 71 | apiClient.setBasePath(_account.getBaseUri() + "/restapi"); 72 | 73 | basePath = apiClient.getBasePath(); 74 | 75 | _token = apiClient.getAccessToken(); 76 | expiresIn = System.currentTimeMillis() + (oAuthToken.getExpiresIn() * 1000); 77 | } 78 | 79 | 80 | private OAuth.Account getAccountInfo(ApiClient client) throws ApiException { 81 | OAuth.UserInfo userInfo = client.getUserInfo(client.getAccessToken()); 82 | OAuth.Account accountInfo = null; 83 | 84 | if(DSConfig.TARGET_ACCOUNT_ID == null || DSConfig.TARGET_ACCOUNT_ID.length() == 0){ 85 | List accounts = userInfo.getAccounts(); 86 | 87 | OAuth.Account acct = this.find(accounts, new ICondition() { 88 | public boolean test(OAuth.Account member) { 89 | return (member.getIsDefault() == "true"); 90 | } 91 | }); 92 | 93 | if (acct != null) return acct; 94 | 95 | acct = this.find(accounts, new ICondition() { 96 | public boolean test(OAuth.Account member) { 97 | return (member.getAccountId() == DSConfig.TARGET_ACCOUNT_ID); 98 | } 99 | }); 100 | 101 | if (acct != null) return acct; 102 | 103 | } 104 | 105 | return accountInfo; 106 | } 107 | 108 | private OAuth.Account find(List accounts, ICondition criteria) { 109 | for (OAuth.Account acct: accounts) { 110 | if(criteria.test(acct)){ 111 | return acct; 112 | } 113 | } 114 | return null; 115 | } 116 | 117 | interface ICondition { 118 | boolean test(T member); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/com/docusign/example/worker/DSConfig.java: -------------------------------------------------------------------------------- 1 | package com.docusign.example.worker; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.Properties; 8 | import java.util.Set; 9 | 10 | /** 11 | * This class consists of static constant configuration value that used by docusign examples 12 | * this members are set once access any member first time. 13 | * configuration can be loaded from environment variables or from properties file config.properties. 14 | * loading order is respectively first trying to load environment variables if not found will try 15 | * load from config.properties file. 16 | */ 17 | public final class DSConfig { 18 | 19 | public static final String CLIENT_ID; 20 | public static final String IMPERSONATED_USER_GUID; 21 | public static final String TARGET_ACCOUNT_ID; 22 | public static final String OAUTH_REDIRECT_URI = "https://www.docusign.com"; 23 | public static final String SIGNER_EMAIL; 24 | public static final String SIGNER_NAME; 25 | public static final String CC_EMAIL; 26 | public static final String CC_NAME; 27 | public static final String PRIVATE_KEY; 28 | public static final String AUTHENTICATION_URL = "https://account-d.docusign.com"; 29 | public static final String DS_AUTH_SERVER; 30 | public static final String API = "restapi/v2"; 31 | public static final String PERMISSION_SCOPES = "signature%20impersonation"; 32 | public static final String JWT_SCOPE = "signature"; 33 | public static final String QUEUE_URL; 34 | public static final String QUEUE_REGION; 35 | public static final String DEBUG; 36 | public static final String SALES_ORDER; 37 | public static final String AWS_ACCOUNT; 38 | public static final String AWS_SECRET; 39 | public static final String ENABLE_BREAK_TEST; 40 | public static final String ENVELOPE_CUSTOM_FIELD; 41 | public static final String OUTPUT_FILE_PREFIX; 42 | public static final String TEST_OUTPUT_DIR_NAME; 43 | public static final String BASIC_AUTH_NAME; 44 | public static final String BASIC_AUTH_PW; 45 | public static final String ENQUEUE_URL; 46 | 47 | public static final String AUD () { 48 | if(DS_AUTH_SERVER != null && DS_AUTH_SERVER.startsWith("https://")) 49 | return DS_AUTH_SERVER.substring(8); 50 | else if(DS_AUTH_SERVER != null && DS_AUTH_SERVER.startsWith("http://")) 51 | return DS_AUTH_SERVER.substring(7); 52 | 53 | return DS_AUTH_SERVER; 54 | } 55 | 56 | static { 57 | // Try load from environment variables 58 | Map config = loadFromEnv(); 59 | 60 | if (config == null) { 61 | // Try load from properties file 62 | config = loadFromProperties(); 63 | } 64 | 65 | CLIENT_ID = fetchValue(config, "DS_CLIENT_ID"); 66 | IMPERSONATED_USER_GUID = fetchValue(config, "DS_IMPERSONATED_USER_GUID"); 67 | TARGET_ACCOUNT_ID = fetchValue(config, "DS_TARGET_ACCOUNT_ID"); 68 | SIGNER_EMAIL = fetchValue(config, "DS_SIGNER_1_EMAIL"); 69 | SIGNER_NAME = fetchValue(config, "DS_SIGNER_1_NAME"); 70 | CC_EMAIL = fetchValue(config, "DS_CC_1_EMAIL"); 71 | CC_NAME = fetchValue(config, "DS_CC_1_NAME"); 72 | PRIVATE_KEY = fetchValue(config, "DS_PRIVATE_KEY"); 73 | DS_AUTH_SERVER = fetchValue(config, "DS_AUTH_SERVER"); // use account.docusign.com for production 74 | DEBUG = fetchValue(config, "DEBUG"); 75 | QUEUE_URL = fetchValue(config, "QUEUE_URL"); 76 | QUEUE_REGION = fetchValue(config, "QUEUE_REGION"); 77 | SALES_ORDER = fetchValue(config, "SALES_ORDER"); 78 | AWS_ACCOUNT = fetchValue(config, "AWS_ACCOUNT"); 79 | AWS_SECRET = fetchValue(config, "AWS_SECRET"); 80 | ENABLE_BREAK_TEST = fetchValue(config, "ENABLE_BREAK_TEST"); 81 | ENVELOPE_CUSTOM_FIELD = fetchValue(config, "ENVELOPE_CUSTOM_FIELD"); 82 | OUTPUT_FILE_PREFIX = fetchValue(config, "OUTPUT_FILE_PREFIX"); 83 | TEST_OUTPUT_DIR_NAME = fetchValue(config, "TEST_OUTPUT_DIR_NAME"); 84 | BASIC_AUTH_NAME = fetchValue(config, "BASIC_AUTH_NAME"); 85 | BASIC_AUTH_PW = fetchValue(config, "BASIC_AUTH_PW"); 86 | ENQUEUE_URL = fetchValue(config, "ENQUEUE_URL"); 87 | 88 | } 89 | 90 | /** 91 | * fetch configuration value by key. 92 | * @param config preloaded configuration key/value map 93 | * @param name key of value 94 | * @return value as string or default empty string 95 | */ 96 | private static String fetchValue(Map config, String name) { 97 | String val = config.get(name); 98 | 99 | if("DS_TARGET_ACCOUNT_ID".equals(name) && "FALSE".equals(val)) { 100 | return null; 101 | } 102 | 103 | return ((val != null) ? val : ""); 104 | } 105 | 106 | 107 | /** 108 | * This method check if environment variables exists and load it into Map 109 | * @return Map of key/value of environment variables if exists otherwise, return null 110 | */ 111 | private static Map loadFromEnv() { 112 | String clientId = System.getenv("DS_CLIENT_ID"); 113 | 114 | if (clientId != null && clientId.length() > 0) { 115 | return System.getenv(); 116 | } 117 | 118 | return null; 119 | } 120 | 121 | /** 122 | * This method load properties located in config.properties file in the working directory. 123 | * 124 | * @return Map of key/value of properties 125 | */ 126 | private static Map loadFromProperties() { 127 | Properties properties = new Properties(); 128 | InputStream input = null; 129 | 130 | try { 131 | input = DSConfig.class.getResourceAsStream("/config.properties"); 132 | properties.load(input); 133 | } catch (IOException e) { 134 | throw new RuntimeException("can not load configuration file", e); 135 | } finally { 136 | if (input != null) { 137 | try { 138 | input.close(); 139 | } catch (IOException e) { 140 | throw new RuntimeException("error occurs will closing input stream: ", e); 141 | } 142 | } 143 | } 144 | 145 | Set> set = properties.entrySet(); 146 | Map mapFromSet = new HashMap(); 147 | 148 | for (Map.Entry entry : set) { 149 | mapFromSet.put((String) entry.getKey(), (String) entry.getValue()); 150 | } 151 | 152 | return mapFromSet; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/test/java/com/docusign/example/test/RunTest.java: -------------------------------------------------------------------------------- 1 | package com.docusign.example.test; 2 | 3 | import java.io.BufferedReader; 4 | import java.io.File; 5 | import java.io.FileReader; 6 | import java.io.IOException; 7 | import java.net.SocketTimeoutException; 8 | import java.net.URL; 9 | import java.nio.file.Paths; 10 | import java.text.SimpleDateFormat; 11 | import java.time.Instant; 12 | import java.util.ArrayList; 13 | import java.util.Base64; 14 | import java.util.Date; 15 | import java.util.concurrent.TimeUnit; 16 | import javax.net.ssl.HttpsURLConnection; 17 | import org.joda.time.DateTime; 18 | import org.junit.Assert; 19 | import org.junit.Test; 20 | import com.docusign.example.worker.DSConfig; 21 | import com.docusign.example.worker.ProcessNotification; 22 | import com.docusign.example.worker.DatePretty; 23 | 24 | public class RunTest { 25 | 26 | private static DateTime timeStart; 27 | private static DateTime[] timeChecks = new DateTime[8]; 28 | private static int timeCheckNumber=0; // 0..6 29 | private static int successes=0; 30 | private static int enqueueErrors=0; 31 | private static int dequeueErrors=0; 32 | public static String mode; // many or few 33 | ArrayList testsSent = new ArrayList(); // test values sent that should also be receieved 34 | private static boolean foundAll = false; 35 | 36 | @Test 37 | public void startTest(String modeName) throws InterruptedException { 38 | mode = modeName; 39 | timeStart = new DateTime(); 40 | for(int i=0; i<=7 ; i++) { 41 | timeChecks[i] = timeStart.plusHours(i+1); 42 | } 43 | System.out.println(DatePretty.date() + "Starting"); 44 | doTests(); 45 | System.out.println(DatePretty.date() + "Done"); 46 | 47 | } 48 | 49 | private void doTests() throws InterruptedException{ 50 | while(timeCheckNumber<=7) { 51 | while (timeChecks[timeCheckNumber].isAfterNow()) { 52 | doTest(); 53 | if(mode=="few") { 54 | DateTime now = new DateTime(); 55 | int sleep = timeChecks[timeCheckNumber].getSecondOfDay()-now.getSecondOfDay()+2; 56 | TimeUnit.SECONDS.sleep(sleep); 57 | } 58 | } 59 | showStatus(); 60 | timeCheckNumber++; 61 | } 62 | showStatus(); 63 | } 64 | 65 | 66 | private void showStatus() { 67 | double rate = (100.0 * successes) / (enqueueErrors + dequeueErrors + successes); 68 | System.out.println(DatePretty.date() + "#### Test statistics: " + successes + " (" + String.format("%.2f", rate) + "%) successes, " + 69 | enqueueErrors + " enqueue errors, " + dequeueErrors + " dequeue errors."); 70 | } 71 | 72 | private void doTest() throws InterruptedException{ 73 | send(); // sets testsSent 74 | DateTime endTime = DateTime.now().plusMinutes(3); 75 | foundAll = false; 76 | int tests = testsSent.size(); 77 | int successesStart = successes; 78 | while(!foundAll && endTime.isAfterNow()) { 79 | TimeUnit.SECONDS.sleep(1); 80 | checkResults(); // sets foundAll and updates testsSent 81 | } 82 | if(!foundAll) { 83 | dequeueErrors += testsSent.size(); 84 | } 85 | 86 | System.out.println("Test: " + tests + " sent. " + (successes - successesStart) + " successes, " + 87 | testsSent.size() + " failures.") ; 88 | } 89 | 90 | 91 | /** 92 | * Look for the reception of the testsSent values 93 | */ 94 | private void checkResults() { 95 | 96 | ArrayList testsReceived = new ArrayList (); 97 | String fileData = ""; 98 | for(int i=0 ; i<=20 ; i++) { 99 | fileData = ""; 100 | try { 101 | // The path of the files created of Test mode 102 | File testDir = new File(Paths.get(ProcessNotification.mainPath, DSConfig.TEST_OUTPUT_DIR_NAME).toString()); 103 | File file = new File(testDir, "test" + i + ".txt"); 104 | if(file.exists() && !file.equals(null)) { 105 | BufferedReader br = new BufferedReader(new FileReader(file)); 106 | fileData = br.readLine(); 107 | br.close(); 108 | } 109 | } 110 | catch (IOException e) { 111 | e.printStackTrace(); 112 | } 113 | if(!fileData.equals(null) && !fileData.isEmpty()) { 114 | testsReceived.add(fileData); 115 | } 116 | } 117 | 118 | // Create a private copy of testsSent (testsSentOrig) and reset testsSent 119 | // Then, for each element in testsSentOrig not found, add back to testsSent. 120 | ArrayList testsSentOrig = new ArrayList(testsSent); 121 | testsSent.clear(); 122 | for(String testValue : testsSentOrig) { 123 | if(testsReceived.contains(testValue)) { 124 | successes++; 125 | } 126 | else { 127 | testsSent.add(testValue); 128 | } 129 | } 130 | 131 | // Update foundAll 132 | foundAll = testsSent.size() == 0; 133 | } 134 | 135 | /** 136 | * Send 5 messages 137 | * @throws InterruptedException 138 | */ 139 | private void send() throws InterruptedException { 140 | testsSent.clear(); 141 | for(int i=0 ; i<5 ; i++) { 142 | try { 143 | long now = Instant.now().toEpochMilli(); 144 | String testValue = "" + now; 145 | send1(testValue); 146 | testsSent.add(testValue); 147 | } 148 | catch(Exception e) { 149 | enqueueErrors ++; 150 | System.out.println(("send: Enqueue error: " + e)); 151 | TimeUnit.SECONDS.sleep(30); 152 | } 153 | } 154 | } 155 | 156 | /** 157 | * Send one enqueue request. Errors will be caught by caller 158 | * @param test the test value 159 | */ 160 | private void send1(String test) { 161 | 162 | try { 163 | URL url = new URL(DSConfig.ENQUEUE_URL + "?test=" + test); 164 | HttpsURLConnection options = (HttpsURLConnection) url.openConnection(); 165 | options.setReadTimeout(1000 * 20); 166 | options.setRequestMethod("GET"); 167 | options.setRequestProperty(DSConfig.AWS_ACCOUNT, DSConfig.AWS_SECRET); 168 | String auth = authObject(); 169 | if(!auth.equals(null)) { 170 | String basicAuth = "Basic " + new String(Base64.getEncoder().encode(auth.getBytes())); 171 | options.setRequestProperty("Authorization", basicAuth); 172 | } 173 | 174 | int responseCode = options.getResponseCode(); 175 | if(responseCode!=HttpsURLConnection.HTTP_OK) { 176 | Assert.fail("send1: GET not worked"); 177 | } 178 | } 179 | catch(SocketTimeoutException e) { 180 | Assert.fail(DatePretty.date() + "send1: SocketTimeoutException - failed to read: " + e); 181 | } 182 | catch(IOException e) { 183 | Assert.fail(DatePretty.date() + "send1: IOException - failed to open url connection: " + e); 184 | } 185 | } 186 | 187 | /** 188 | * Returns a string for the HttpsURLConnection request 189 | * @return Authorization that contains DSConfig.BASIC_AUTH_NAME and DSConfig.BASIC_AUTH_PW if exist 190 | */ 191 | private String authObject() { 192 | if (!DSConfig.BASIC_AUTH_NAME.equals(null) && DSConfig.BASIC_AUTH_NAME != "{BASIC_AUTH_NAME}" 193 | && !DSConfig.BASIC_AUTH_PW.equals(null) && DSConfig.BASIC_AUTH_PW != "{BASIC_AUTH_PS}" ) { 194 | return DSConfig.BASIC_AUTH_NAME + ":" + DSConfig.BASIC_AUTH_PW; 195 | } 196 | else { 197 | return null; 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /src/test/java/com/docusign/example/test/CreateEnvelope.java: -------------------------------------------------------------------------------- 1 | package com.docusign.example.test; 2 | 3 | import java.io.IOException; 4 | import java.util.ArrayList; 5 | import java.util.Arrays; 6 | import java.util.List; 7 | import com.sun.jersey.core.util.Base64; 8 | import com.docusign.esign.api.EnvelopesApi; 9 | import com.docusign.esign.client.ApiClient; 10 | import com.docusign.esign.client.ApiException; 11 | import com.docusign.esign.model.*; 12 | import com.docusign.example.worker.DSConfig; 13 | import com.docusign.example.worker.JWTAuth; 14 | 15 | 16 | class CreateEnvelope extends JWTAuth { 17 | 18 | private static final String DOC_2_DOCX = "World_Wide_Corp_Battle_Plan_Trafalgar.docx"; 19 | 20 | private static final String DOC_3_PDF = "World_Wide_Corp_lorem.pdf"; 21 | private static final String ENVELOPE_1_DOCUMENT_1 = "" + 22 | "" + 23 | " " + 24 | " " + 25 | " " + 26 | " " + 27 | "

World Wide Corp

" + 29 | "

Order Processing Division

" + 32 | "

Ordered by " + DSConfig.SIGNER_NAME + "

" + 33 | "

Email: " + DSConfig.SIGNER_EMAIL + "

" + 34 | "

Copy to: " + DSConfig.CC_NAME + ", " + DSConfig.SIGNER_EMAIL + "

" + 35 | "

" + 36 | " Candy bonbon pastry jujubes lollipop wafer biscuit biscuit. Topping brownie sesame snaps" + 37 | " sweet roll pie. Croissant danish biscuit soufflé caramels jujubes jelly. Dragée danish caramels lemon" + 38 | " drops dragée. Gummi bears cupcake biscuit tiramisu sugar plum pastry." + 39 | " Dragée gummies applicake pudding liquorice. Donut jujubes oat cake jelly-o. Dessert bear claw chocolate" + 40 | " cake gummies lollipop sugar plum ice cream gummies cheesecake." + 41 | "

" + 42 | " " + 43 | "

Agreed: **signature_1**/

" + 44 | " " + 45 | ""; 46 | 47 | 48 | public CreateEnvelope(ApiClient apiClient) throws IOException { 49 | super(apiClient); 50 | } 51 | 52 | /** 53 | * method show the usage of 54 | * @return 55 | * @throws ApiException 56 | * @throws IOException 57 | */ 58 | public EnvelopeSummary sendEnvelope() throws ApiException, IOException { 59 | 60 | this.checkToken(); 61 | 62 | EnvelopeDefinition envelopeDefinition = new EnvelopeDefinition(); 63 | envelopeDefinition.setEmailSubject("Document sent from the Test Mode"); 64 | 65 | Document doc1 = new Document(); 66 | doc1.setDocumentBase64(new String(Base64.encode(ENVELOPE_1_DOCUMENT_1.getBytes()))); 67 | doc1.setName("Order acknowledgement"); 68 | doc1.setFileExtension("html"); 69 | doc1.setDocumentId("1"); 70 | 71 | Document doc2 = new Document(); 72 | doc2.setDocumentBase64(new String(Base64.encode(DSHelper.readContent(DOC_2_DOCX)))); 73 | doc2.setName("Battle Plan"); 74 | doc2.setFileExtension("docx"); 75 | doc2.setDocumentId("2"); 76 | 77 | Document doc3 = new Document(); 78 | doc3.setDocumentBase64(new String(Base64.encode(DSHelper.readContent(DOC_3_PDF)))); 79 | doc3.setName("Lorem Ipsum"); 80 | doc3.setFileExtension("pdf"); 81 | doc3.setDocumentId("3"); 82 | 83 | // The order in the docs array determines the order in the envelope 84 | envelopeDefinition.setDocuments(Arrays.asList(doc1, doc2, doc3)); 85 | // create a signer recipient to sign the document, identified by name and email 86 | // We're setting the parameters via the object creation 87 | Signer signer1 = new Signer(); 88 | signer1.setEmail(DSConfig.SIGNER_EMAIL); 89 | signer1.setName(DSConfig.SIGNER_NAME); 90 | signer1.setRecipientId("1"); 91 | signer1.setRoutingOrder("1"); 92 | // routingOrder (lower means earlier) determines the order of deliveries 93 | // to the recipients. Parallel routing order is supported by using the 94 | // same integer as the order for two or more recipients. 95 | 96 | 97 | // Create signHere fields (also known as tabs) on the documents, 98 | // We're using anchor (autoPlace) positioning 99 | // 100 | // The DocuSign platform seaches throughout your envelope's 101 | // documents for matching anchor strings. So the 102 | // sign_here_2 tab will be used in both document 2 and 3 since they 103 | // use the same anchor string for their "signer 1" tabs. 104 | SignHere signHere1 = new SignHere(); 105 | signHere1.setAnchorString("**signature_1**"); 106 | signHere1.setAnchorUnits("pixels"); 107 | signHere1.setAnchorXOffset("20"); 108 | signHere1.anchorYOffset("10"); 109 | 110 | SignHere signHere2 = new SignHere(); 111 | signHere2.setAnchorString("/sn1/"); 112 | signHere2.setAnchorUnits("pixels"); 113 | signHere2.setAnchorXOffset("20"); 114 | signHere2.anchorYOffset("10"); 115 | // Tabs are set per recipient / signer 116 | Tabs tabs = new Tabs(); 117 | tabs.setSignHereTabs(Arrays.asList(signHere1, signHere2)); 118 | signer1.setTabs(tabs); 119 | // Add the recipients to the envelope object 120 | Recipients recipients = new Recipients(); 121 | recipients.setSigners(Arrays.asList(signer1)); 122 | envelopeDefinition.setRecipients(recipients); 123 | 124 | // Request that the envelope be sent by setting |status| to "sent". 125 | // To request that the envelope be created as a draft, set to "created" 126 | envelopeDefinition.setStatus("sent"); 127 | 128 | // Add the customFields to the envelope 129 | TextCustomField textCustomField = new TextCustomField(); 130 | textCustomField.setValue("Test_Mode"); 131 | textCustomField.setShow("true"); 132 | textCustomField.setName("Sales order"); 133 | CustomFields customFields = new CustomFields(); 134 | List textCustomFields = new ArrayList(); 135 | textCustomFields.add(textCustomField); 136 | customFields.setTextCustomFields(textCustomFields ); 137 | envelopeDefinition.setCustomFields(customFields); 138 | 139 | 140 | EnvelopesApi envelopeApi = new EnvelopesApi(this.apiClient); 141 | EnvelopeSummary results = envelopeApi.createEnvelope(this.getAccountId(), envelopeDefinition); 142 | 143 | return results; 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Java: Connect Worker for AWS 2 | 3 | Repository: [connect-java-worker-aws](https://github.com/docusign/connect-java-worker-aws). 4 | 5 | ## Introduction 6 | 7 | This is an example worker application for 8 | Connect webhook notification messages sent 9 | via the [AWS SQS (Simple Queueing System)](https://aws.amazon.com/sqs/). 10 | 11 | This application receives DocuSign Connect 12 | messages from the queue and then processes them: 13 | 14 | * If the envelope is complete, the application 15 | uses a DocuSign JWT Grant token to retrieve 16 | the envelope's combined set of documents, 17 | and stores them in the `output` directory. 18 | 19 | For this example, the envelope **must** 20 | include an Envelope Custom Field 21 | named `Sales order.` The Sales order field is used 22 | to name the output file. 23 | 24 | ## Architecture 25 | 26 | ![Connect listener architecture](docs/connect_listener_architecture.png) 27 | 28 | AWS has [SQS](https://aws.amazon.com/tools/) 29 | SDK libraries for C#, Java, Node.js, Python, Ruby, C++, and Go. 30 | 31 | ## Installation 32 | 33 | ### Introduction 34 | First, install the **Lambda listener** on AWS and set up the SQS queue. 35 | 36 | Then set up this code example to receive and process the messages 37 | received via the SQS queue. 38 | 39 | ### Eclipse installation 40 | 41 | See the [Eclipse instructions](https://github.com/docusign/connect-java-worker-aws/blob/master/docs/Readme.Eclipse.md) 42 | 43 | ### Installing the Lambda Listener 44 | 45 | Install the example 46 | [Connect listener for AWS](https://github.com/docusign/connect-node-listener-aws) 47 | on AWS. 48 | At the end of this step, you will have the 49 | `Queue URL`, `Queue Region` and `Enqueue url` that you need for the next step. 50 | 51 | ### Installing the worker (this repository) 52 | 53 | #### Requirements 54 | Install the latest Long Term Support version of 55 | Java v1.7, v1.8 or later on your system. 56 | 57 | 1. Download or clone this repository. 58 | 59 | 1. Using AWS IAM, create an IAM `User` with 60 | access to your SQS queue. 61 | Record the IAM user's AWS Access Key and Secret. 62 | Configure environment variables 63 | `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` with the 64 | IAM user credentials. 65 | 66 | 1. Configure a DocuSign Integration Key for the application. 67 | The application uses the OAuth JWT Grant flow. 68 | If consent has not been granted to the application by 69 | the user, then the application provides a url 70 | that can be used to grant individual consent. 71 | 72 | 1. Download this repository to a directory. 73 | 74 | 1. Configure `config.properties` or set the 75 | environment variables as indicated in that file. 76 | 77 | 78 | ### Short installation instructions 79 | * Download or clone this repository. 80 | * The project includes a Maven pom file. 81 | * Configure the project's resource file: 82 | 83 | `connect-java-worker-aws/src/main/resources/config.properties` 84 | See the Configuration section, below, 85 | for more information. 86 | * The project's main class is 87 | `com.docusign.example.jwt.AWSWorker` 88 | 89 | 90 | ## Configure the example 91 | 92 | You can configure the example either via a properties file or via 93 | environment variables: 94 | 95 | * **config.properties:** In the **src/main/resources/** 96 | directory, edit the `config.properties` file to update 97 | it with your settings. 98 | More information for the configuration settings is below. 99 | * Or via **environment variables:** export the needed 100 | environment variables. 101 | The variable names in the `config.properties` file 102 | are the same as the needed environment variables. 103 | 104 | **Note:** do not store your Integration Key, private key, or other 105 | private information in your code repository. 106 | 107 | #### Creating the Integration Key 108 | Your DocuSign Integration Key must be configured for a JWT OAuth authentication flow: 109 | * Using the DocuSign Admin tool, 110 | create a public/private key pair for the integration key. 111 | Store the private key 112 | in a secure location. You can use a file or a key vault. 113 | * The example requires the private key. Store the private key in the 114 | `config.properties` file or in the environment variable 115 | `DS_PRIVATE_KEY`. 116 | * Due to Java handling of multi-line strings, add the 117 | text `\n\` at the end of each line of the private key. 118 | See the example in the `config.properties` file. 119 | * If you will be using individual consent grants, you must create a 120 | `Redirect URI` for the key. Any URL can be used. By default, this 121 | example uses `https://www.docusign.com` 122 | 123 | ```` 124 | # private key string 125 | # NOTE: the Java config file parser requires that you 126 | # add \n\ at the ending of every line 127 | # DS_PRIVATE_KEY=\n\ 128 | -----BEGIN RSA PRIVATE KEY-----\n\ 129 | MIIEpAIBAAKCAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n\ 130 | MIIEpAIBAAKCAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n\ 131 | MIIEpAIBAAKCAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n\ 132 | MIIEpAIBAAKCAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n\ 133 | MIIEpAIBAAKCAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n\ 134 | MIIEpAIBAAKCAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n\ 135 | MIIEpAIBAAKCAXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n\ 136 | ... 137 | UC1WqwKBgQCY/6aZxlWX9XYgsQsnUjhj2aTwr7pCiQuYceIzLTQzy+nz8M4PfCE1\n\ 138 | rjRsm6YTpoxh7nuW2qnFfMA58UPs9tonN/z1pr9mKfwmamtPXeMSJeEZUVmh7mNx\n\ 139 | PEHgznlGh/vUboCuA4tQOcKytxFfKG4F+jM/g4GH9z46KZOow3Hb6g==\n\ 140 | -----END RSA PRIVATE KEY----- 141 | ```` 142 | ## Run the examples 143 | 144 | The project's main class is `com.docusign.example.jwt.AWSWorker` 145 | 146 | ## Testing 147 | Configure a DocuSign Connect subscription to send notifications to 148 | the Cloud Function. Create / complete a DocuSign envelope. 149 | The envelope **must include an Envelope Custom Field named "Sales order".** 150 | 151 | * Check the Connect logs for feedback. 152 | * Check the console output of this app for log output. 153 | * Check the `output` directory to see if the envelope's 154 | combined documents and CoC were downloaded. 155 | 156 | For this code example, the 157 | envelope's documents will only be downloaded if 158 | the envelope is `complete` and includes a 159 | `Sales order` custom field. 160 | 161 | ## Unit Tests 162 | Includes three types of testing: 163 | * [SavingEnvelopeTest.cs](UnitTests/SavingEnvelopeTest.cs) allow you to send an envelope to your amazon sqs from the program. The envelope is saved at `output` directory although its status is `sent`. 164 | 165 | * [RunTest.cs](UnitTests/RunTest.cs) divides into two types of tests, both submits tests for 8 hours and updates every hour about the amount of successes or failures that occurred in that hour, the differences between the two are: 166 | * `few` - Submits 5 tests every hour. 167 | * `many` - Submits many tests every hour. 168 | 169 | In order to run the tests you need to first run the program. then choose the wanted test and run it also. 170 | 171 | ## Support, Contributions, License 172 | 173 | Submit support questions to [StackOverflow](https://stackoverflow.com). Use tag `docusignapi`. 174 | 175 | Contributions via Pull Requests are appreciated. 176 | All contributions must use the MIT License. 177 | 178 | This repository uses the MIT license, see the 179 | [LICENSE](https://github.com/docusign/connect-java-worker-aws/blob/master/LICENSE) file. 180 | -------------------------------------------------------------------------------- /src/main/java/com/docusign/example/worker/AWSWorker.java: -------------------------------------------------------------------------------- 1 | package com.docusign.example.worker; 2 | 3 | import java.text.SimpleDateFormat; 4 | import java.util.Date; 5 | import java.util.LinkedList; 6 | import java.util.List; 7 | import java.util.Queue; 8 | import java.util.concurrent.TimeUnit; 9 | import org.json.JSONException; 10 | import org.json.JSONObject; 11 | import com.amazonaws.auth.AWSStaticCredentialsProvider; 12 | import com.amazonaws.auth.BasicAWSCredentials; 13 | import com.amazonaws.services.sqs.AmazonSQS; 14 | import com.amazonaws.services.sqs.AmazonSQSClientBuilder; 15 | import com.amazonaws.services.sqs.model.Message; 16 | import com.amazonaws.services.sqs.model.ReceiveMessageRequest; 17 | import com.docusign.esign.client.ApiClient; 18 | import com.docusign.esign.client.ApiException; 19 | 20 | public class AWSWorker { 21 | 22 | private static final ApiClient apiClient = new ApiClient(); 23 | private static BasicAWSCredentials creds = new BasicAWSCredentials(DSConfig.AWS_ACCOUNT, DSConfig.AWS_SECRET); 24 | private static AmazonSQS queue = AmazonSQSClientBuilder.standard().withCredentials(new AWSStaticCredentialsProvider(creds)).withRegion(DSConfig.QUEUE_REGION).build(); 25 | private static Queue checkLogQ = new LinkedList(); 26 | private static String queueUrl = DSConfig.QUEUE_URL; 27 | private static boolean restart = true; //restart the program if any error has occurred 28 | 29 | public static void main(String args[]) throws Exception { 30 | 31 | listenForever(); 32 | 33 | } 34 | 35 | /** 36 | * The function will listen forever, dispatching incoming notifications 37 | * to the processNotification library. 38 | * See https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/sqs-examples-send-receive-messages.html#sqs-examples-send-receive-messages-receiving 39 | * @throws Exception while trying to sleep for 5 seconds 40 | */ 41 | private static void listenForever() throws Exception { 42 | // Check that we can get a DocuSign token 43 | testToken(); 44 | 45 | while(true) { 46 | if(restart) { 47 | System.out.println(DatePretty.date() + "Starting queue worker"); 48 | restart = false; 49 | // Start the queue worker 50 | startQueue(); 51 | } 52 | TimeUnit.SECONDS.sleep(5); 53 | } 54 | } 55 | 56 | /** 57 | * Check that we can get a DocuSign token and handle common error 58 | * cases: ds_configuration not configured, need consent. 59 | * @throws Exception while trying to get access to token 60 | */ 61 | private static void testToken() throws Exception { 62 | try { 63 | if(DSConfig.CLIENT_ID.equals("{CLIENT_ID}")) { 64 | System.err.println("Problem: you need to configure this example, either via environment variables (recommended) \n" + 65 | "or via the ds_configuration.js file. \n" + 66 | "See the README file for more information\n\n"); 67 | System.exit(0); 68 | } 69 | JWTAuth dsJWTAuth = new JWTAuth(apiClient); 70 | dsJWTAuth.checkToken(); 71 | } 72 | 73 | // An API problem 74 | catch (ApiException e) { 75 | // Special handling for consent_required 76 | String message = e.getMessage(); 77 | if(message != null && message.contains("consent_required")){ 78 | String consent_url = String.format("%s/oauth/auth?response_type=code&scope=%s" + 79 | "&client_id=%s" + 80 | "&redirect_uri=%s", 81 | DSConfig.DS_AUTH_SERVER, 82 | DSConfig.PERMISSION_SCOPES, 83 | DSConfig.CLIENT_ID, 84 | DSConfig.OAUTH_REDIRECT_URI); 85 | System.err.println("\nC O N S E N T R E Q U I R E D" + 86 | "\nAsk the user who will be impersonated to run the following url: " + 87 | "\n"+ consent_url+ 88 | "\n\nIt will ask the user to login and to approve access by your application." + 89 | "\nAlternatively, an Administrator can use Organization Administration to" + 90 | "\npre-approve one or more users."); 91 | System.exit(0); 92 | } 93 | else { 94 | // Some other DocuSign API problem 95 | System.err.println(String.format(" Reason: %d", e.getCode())); 96 | System.err.println(String.format(" Error Reponse: %s", e.getResponseBody())); 97 | System.exit(0); 98 | } 99 | } 100 | // Not an API problem 101 | catch(Exception e) { 102 | System.err.println(DatePretty.date() + e.getMessage()); 103 | } 104 | } 105 | 106 | /** 107 | * Receive and wait for messages from queue 108 | * @throws Exception catches all types of errors that may occur during the program 109 | */ 110 | private static void startQueue() throws Exception { 111 | 112 | try { 113 | // Receive messages from queue, maximum waits for 20 seconds for message 114 | ReceiveMessageRequest receive_request = new ReceiveMessageRequest().clone() 115 | .withMaxNumberOfMessages(10) 116 | .withQueueUrl(queueUrl) 117 | .withWaitTimeSeconds(20); 118 | 119 | while(true) { 120 | addCheckLogQ(DatePretty.date() + "Awaiting a message..."); 121 | // The List will contain all the queue messages 122 | List messages = queue.receiveMessage(receive_request).getMessages(); 123 | // The amount of messages received 124 | int msgCount = messages.size(); 125 | addCheckLogQ(DatePretty.date() + "found " + msgCount + " message(s)"); 126 | // If at least one message has been received 127 | if(msgCount!=0) { 128 | printCheckLogQ(); 129 | for(Message m: messages) { 130 | messageHandler(m, queue); 131 | } 132 | } 133 | } 134 | } 135 | // Catches all types of errors that may occur during the program 136 | catch(Exception e) { 137 | printCheckLogQ(); 138 | System.err.println("\n"+DatePretty.date() + "Queue receive error:"); 139 | System.err.println(e.getMessage()); 140 | TimeUnit.SECONDS.sleep(5); 141 | // Restart the program 142 | restart = true; 143 | } 144 | } 145 | 146 | 147 | /** 148 | * Maintain the array checkLogQ as a FIFO buffer with length 4. 149 | * When a new entry is added, remove oldest entry and shuffle. 150 | * @param message the message received from queue 151 | */ 152 | private static void addCheckLogQ(String message) { 153 | int length = 4; 154 | // If checkLogQ size is smaller than 4 add the message 155 | if(checkLogQ.size() < length) { 156 | checkLogQ.add(message); 157 | } 158 | // If checkLogQ size is bigger than 4 159 | else { 160 | // Remove the oldest message and add the new one 161 | checkLogQ.remove(); 162 | checkLogQ.add(message); 163 | } 164 | } 165 | 166 | /** 167 | * Prints all checkLogQ messages to the console 168 | */ 169 | private static void printCheckLogQ() { 170 | // Prints all the elements in the checkLogQ 171 | for(String message : checkLogQ) { 172 | System.out.println(message); 173 | } 174 | checkLogQ.clear(); // Reset 175 | } 176 | 177 | /** 178 | * Process a message 179 | * See https://github.com/Azure/azure-sdk-for-js/tree/master/sdk/servicebus/service-bus#register-message-handler 180 | * @param message the received message from queue 181 | * @param queue contains all the info of the AmazonSQS queue 182 | * @throws Exception while creating JSON object or while trying to convert null object to string 183 | */ 184 | private static void messageHandler(Message message, AmazonSQS queue) throws Exception { 185 | 186 | String test = ""; 187 | String xml = null; 188 | // If there is an error the program will catch it and the JSONCreated will change to false 189 | boolean JSONCreated = true; 190 | 191 | 192 | if(DSConfig.DEBUG.equals("true")) { 193 | String str = "Processing message id " + message.getMessageId(); 194 | System.out.println(DatePretty.date() + str); 195 | } 196 | try { 197 | // Parse the information from message body. the information contains contains fields like test and xml 198 | String body = message.getBody(); 199 | JSONObject jsonObject = new JSONObject(body); 200 | xml = (String) jsonObject.get("xml"); 201 | // Used in the test mode 202 | test = (String) jsonObject.get("test"); 203 | 204 | } 205 | // Catch exceptions while trying to create a JSON object 206 | catch(JSONException e) { 207 | System.err.println(DatePretty.date() + e.getMessage()); 208 | JSONCreated = false; 209 | } 210 | // Catch java.lang exceptions (trying to convert null to String) - make sure your message contains both those fields 211 | catch(Exception e) { 212 | System.err.println(DatePretty.date() + e.getMessage()); 213 | JSONCreated = false; 214 | } 215 | // If JSON object created successfully - continue 216 | if(JSONCreated) { 217 | ProcessNotification.process(test, xml); 218 | } 219 | // If JSON object wasn't created - ignore message 220 | else { 221 | String errorMessage = "Null or bad body in message id " + message.getMessageId() + ". Ignoring."; 222 | System.out.println(DatePretty.date() + errorMessage); 223 | } 224 | // Delete received message from queue 225 | queue.deleteMessage(queueUrl, message.getReceiptHandle()); 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/main/java/com/docusign/example/worker/ProcessNotification.java: -------------------------------------------------------------------------------- 1 | package com.docusign.example.worker; 2 | 3 | import com.docusign.esign.api.EnvelopesApi; 4 | import com.docusign.esign.client.ApiClient; 5 | import com.sun.jersey.api.client.ClientHandlerException; 6 | import org.w3c.dom.Document; 7 | import org.w3c.dom.Node; 8 | import org.w3c.dom.NodeList; 9 | import org.xml.sax.InputSource; 10 | 11 | import javax.xml.parsers.DocumentBuilder; 12 | import javax.xml.parsers.DocumentBuilderFactory; 13 | import javax.xml.xpath.XPath; 14 | import javax.xml.xpath.XPathConstants; 15 | import javax.xml.xpath.XPathExpression; 16 | import javax.xml.xpath.XPathFactory; 17 | import java.io.File; 18 | import java.io.FileWriter; 19 | import java.io.StringReader; 20 | import java.nio.file.Files; 21 | import java.nio.file.Path; 22 | import java.nio.file.Paths; 23 | import java.util.concurrent.TimeUnit; 24 | import java.util.regex.Matcher; 25 | import java.util.regex.Pattern; 26 | 27 | public class ProcessNotification { 28 | 29 | private static String envelopeId; 30 | private static String subject; 31 | private static String senderName; 32 | private static String senderEmail; 33 | private static String status; 34 | private static String created; 35 | private static boolean completed; 36 | private static String completedMsg; 37 | private static String orderNumber; 38 | // Access to the current working directory - in order to save the folders in the right path 39 | public static String mainPath = Paths.get(".").toAbsolutePath().normalize().toString(); 40 | 41 | /** 42 | * Process the notification message 43 | * @param test checks whether this is a regular message or test mode message 44 | * @param xml contains all the information of the envelope 45 | * @throws Exception failed to create file or failed to save the document - caught at startQueue method 46 | */ 47 | public static void process(String test, String xml) throws Exception{ 48 | 49 | // Guarding against injection attacks 50 | String pattern = "[^0-9]"; 51 | // Create a Pattern object 52 | Pattern regex = Pattern.compile(pattern); 53 | // Now create matcher object. 54 | Matcher matcher = regex.matcher(test); 55 | // Check the incoming test variable to ensure that it ONLY contains the expected data (empty string "", "/break" or integers string) 56 | // matcher.find() equals true when it finds wrong input 57 | boolean validInput = test.equals("/break") || test.isEmpty() || !matcher.find(); 58 | if (validInput) { 59 | if(!test.isEmpty()) { 60 | // Message from test mode 61 | processTest(test); 62 | } 63 | } 64 | else { 65 | System.err.println(DatePretty.date() + "Wrong test value: " + test); 66 | System.err.println("test can only be: /break, empty string or integers string"); 67 | } 68 | 69 | // In test mode there is no xml sting, should be checked before trying to parse it 70 | if(!xml.isEmpty()) { 71 | DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 72 | // Guarding against an XML external entity injection attack 73 | dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); 74 | dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); 75 | dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); 76 | 77 | InputSource is = new InputSource(); 78 | is.setCharacterStream(new StringReader(xml)); 79 | DocumentBuilder db = dbf.newDocumentBuilder(); 80 | Document doc = db.parse(is); 81 | 82 | // To get the nodes direct children of use //EnvelopeStatus/node_name 83 | XPath xpath = XPathFactory.newInstance().newXPath(); 84 | XPathExpression xpathExpression = xpath.compile("//EnvelopeStatus/EnvelopeID"); 85 | NodeList envelopeStatusNodes = (NodeList)xpathExpression.evaluate(doc, XPathConstants.NODESET); 86 | Node file = envelopeStatusNodes.item(0); 87 | envelopeId = file.getTextContent(); 88 | 89 | xpathExpression = xpath.compile("//EnvelopeStatus/CustomFields/CustomField/Name"); 90 | envelopeStatusNodes = (NodeList)xpathExpression.evaluate(doc, XPathConstants.NODESET); 91 | file = envelopeStatusNodes.item(0); 92 | if(file.getTextContent().equals(DSConfig.ENVELOPE_CUSTOM_FIELD)) { 93 | xpathExpression = xpath.compile("//EnvelopeStatus/CustomFields/CustomField/Value"); 94 | envelopeStatusNodes = (NodeList)xpathExpression.evaluate(doc, XPathConstants.NODESET); 95 | file = envelopeStatusNodes.item(0); 96 | orderNumber = file.getTextContent(); 97 | } 98 | else { 99 | orderNumber=null; 100 | } 101 | 102 | xpathExpression = xpath.compile("//EnvelopeStatus/Status"); 103 | envelopeStatusNodes = (NodeList)xpathExpression.evaluate(doc, XPathConstants.NODESET); 104 | file = envelopeStatusNodes.item(0); 105 | status = file.getTextContent(); 106 | 107 | if(status.equals("Completed")){ 108 | completed = true; 109 | completedMsg = "Completed " + completed; 110 | } 111 | else { 112 | completed = false; 113 | completedMsg = ""; 114 | } 115 | 116 | xpathExpression = xpath.compile("//EnvelopeStatus/Subject"); 117 | envelopeStatusNodes = (NodeList)xpathExpression.evaluate(doc, XPathConstants.NODESET); 118 | file = envelopeStatusNodes.item(0); 119 | subject = file.getTextContent(); 120 | 121 | xpathExpression = xpath.compile("//EnvelopeStatus/UserName"); 122 | envelopeStatusNodes = (NodeList)xpathExpression.evaluate(doc, XPathConstants.NODESET); 123 | file = envelopeStatusNodes.item(0); 124 | senderName = file.getTextContent(); 125 | 126 | xpathExpression = xpath.compile("//EnvelopeStatus/Email"); 127 | envelopeStatusNodes = (NodeList)xpathExpression.evaluate(doc, XPathConstants.NODESET); 128 | file = envelopeStatusNodes.item(0); 129 | senderEmail = file.getTextContent(); 130 | 131 | xpathExpression = xpath.compile("//EnvelopeStatus/Created"); 132 | envelopeStatusNodes = (NodeList)xpathExpression.evaluate(doc, XPathConstants.NODESET); 133 | file = envelopeStatusNodes.item(0); 134 | created = file.getTextContent(); 135 | 136 | 137 | // For debugging, you can print the entire notification 138 | System.out.println("EnvelopeId: " + envelopeId); 139 | System.out.println("Status: " + status); 140 | System.out.println("Order Number: " + orderNumber); 141 | System.out.println("Subject: " + subject); 142 | System.out.println("Sender: " + senderName + ", " + senderEmail); 143 | System.out.println("Sent: " + created + ", " + completedMsg); 144 | 145 | // Step 2. Filter the notifications 146 | boolean ignore = false; 147 | // Guarding against injection attacks 148 | // Check the incoming orderNumber variable to ensure that it ONLY contains the expected data ("Test_Mode" or integers string) 149 | // Envelope might not have Custom field when orderNumber == null 150 | // matcher.find() equals true when it finds wrong input 151 | matcher = regex.matcher(orderNumber); 152 | validInput = orderNumber.equals("Test_Mode") || !matcher.find() || orderNumber == null; 153 | if(validInput) { 154 | // Check if the envelope was sent from the test mode 155 | // If sent from test mode - ok to continue even if the status != Completed 156 | if(!orderNumber.equals("Test_Mode")) { 157 | if(!status.equals("Completed")) { 158 | ignore = true; 159 | if(DSConfig.DEBUG.equals("true")) { 160 | System.err.println(DatePretty.date() + "IGNORED: envelope status is " + status); 161 | } 162 | } 163 | } 164 | 165 | if(orderNumber == null) { 166 | ignore = true; 167 | if(DSConfig.DEBUG.equals("true")) { 168 | System.err.println(DatePretty.date() + "IGNORED: envelope does not have a " + 169 | DSConfig.ENVELOPE_CUSTOM_FIELD + " envelope custom field."); 170 | } 171 | } 172 | } 173 | else { 174 | ignore = true; 175 | System.err.println(DatePretty.date() + "Wrong orderNumber value: " + orderNumber); 176 | System.err.println("orderNumber can only be: Test_Mode or integers string"); 177 | } 178 | 179 | // Step 3. (Future) Check that this is not a duplicate notification 180 | // The queuing system delivers on an "at least once" basis. So there is a 181 | // chance that we have already processes this notification. 182 | // 183 | // For this example, we'll just repeat the document fetch if it is duplicate notification 184 | 185 | // Step 4 save the document - it can raise an exception which will be caught at startQueue 186 | if(!ignore) { 187 | saveDoc(envelopeId, orderNumber); 188 | } 189 | } 190 | } 191 | 192 | /** 193 | * Creates a new file that contains the envelopeId and orderNumber 194 | * @param envelopeId parse from the message 195 | * @param orderNumber parse from the message 196 | * @throws Exception failed to create file or failed to save the document 197 | */ 198 | private static void saveDoc(String envelopeId, String orderNumber) throws Exception { 199 | 200 | try { 201 | ApiClient dsApiClient = new ApiClient(); 202 | JWTAuth dsJWTAuth = new JWTAuth(dsApiClient); 203 | // Checks for the token before calling the function getToken 204 | dsJWTAuth.checkToken(); 205 | dsApiClient.setBasePath(dsJWTAuth.getBasePath()); 206 | dsApiClient.setAccessToken(dsJWTAuth.getToken(), (long) 600); 207 | //dsApiClient.addDefaultHeader("Authorization", "Bearer " + dsJWTAuth.getToken()); 208 | EnvelopesApi envelopesApi = new EnvelopesApi(dsApiClient); 209 | 210 | 211 | // Create the output directory if needed 212 | File file = new File( Paths.get(mainPath, "output").toString()); 213 | if (!file.exists()) { 214 | if (!file.mkdir()) { 215 | throw new Exception(DatePretty.date() + "Failed to create directory"); 216 | } 217 | } 218 | 219 | byte[] results = envelopesApi.getDocument(dsJWTAuth.getAccountId(), envelopeId, "combined"); 220 | Path path = Paths.get(mainPath,"output", DSConfig.OUTPUT_FILE_PREFIX + orderNumber + ".pdf"); 221 | 222 | try { 223 | // Create the output file 224 | Files.write(path, results); 225 | // In order to see the file click F5 or right click on output folder at the Eclipse Project Explorer and refresh. 226 | // If the file won't open inside the Eclipse Try Window > Preferences > General > Editors > File Associations. 227 | // Add *.pdf if it is not there. Highlight it and then add an associated editor. 228 | // Select the External programs radio and then Adobe Acrobat Document or another reader program. 229 | // You can also open it from your computer file explorer. 230 | } 231 | // Catch exception if failed to create file 232 | catch(Exception e) { 233 | throw new Exception(DatePretty.date() + "Failed to create file"); 234 | } 235 | // Catch exception if BREAK_TEST equals to true or if orderNumber contains "/break" 236 | if(DSConfig.ENABLE_BREAK_TEST.equals("true") && ("" + orderNumber).contains("/break")) { 237 | throw new Exception (DatePretty.date() + "Break test"); 238 | } 239 | } 240 | catch (ClientHandlerException e) { 241 | System.err.println("ClientHandlerException: " + e.getMessage()); 242 | } 243 | 244 | // Catch exception while fetching and saving docs for envelope 245 | catch(Exception e) { 246 | System.err.println(DatePretty.date() + 247 | "Error while fetching and saving docs for envelope " + envelopeId + ", order " + orderNumber); 248 | System.err.println(e.getMessage()); 249 | throw new Exception (DatePretty.date() + "saveDoc error"); 250 | } 251 | } 252 | 253 | /** 254 | * Process test details into files 255 | * @param test contains the test number 256 | * @throws Exception if failed to create directory or failed to rename a file name 257 | */ 258 | private static void processTest(String test) throws Exception { 259 | 260 | // Exit the program if BREAK_TEST equals to true or if orderNumber contains "/break" 261 | if(DSConfig.ENABLE_BREAK_TEST.equals("true") && ("" + test).contains("/break")){ 262 | System.err.println(DatePretty.date() + "BREAKING worker test!"); 263 | System.exit(2); 264 | } 265 | 266 | System.out.println("Processing test value " + test.toString()); 267 | 268 | // Create the test_messages directory if needed 269 | String testDirName = DSConfig.TEST_OUTPUT_DIR_NAME; 270 | File testDir = new File(Paths.get(mainPath, testDirName).toString()); 271 | if (!testDir.exists()) { 272 | if (!testDir.mkdir()) { 273 | throw new Exception(DatePretty.date() + "Failed to create directory"); 274 | } 275 | } 276 | 277 | // First shuffle test1 to test2 (if it exists) and so on 278 | for(int i=9 ; i>0 ; i--) { 279 | File oldFile = new File(testDir, "test" + i + ".txt"); 280 | File newFile = new File(testDir, "test" + (i+1) + ".txt"); 281 | if(oldFile.exists() && !oldFile.equals(null)) { 282 | // Rename the file name - only works if newFile name does not exist 283 | if(newFile.exists()) { 284 | newFile.delete(); 285 | } 286 | // Throw exception if could not rename the file name 287 | // Try to rename 3 times before giving up 288 | boolean renameWorked = false; 289 | for(int j=0; j<3 && !renameWorked ; j++) { 290 | if(!oldFile.renameTo(newFile)) { 291 | TimeUnit.MILLISECONDS.sleep(500); 292 | if(j==2) { 293 | throw new Exception(DatePretty.date() + "Could not rename "+ oldFile.getName() + " to " + newFile.getName()); 294 | } 295 | } 296 | else{ 297 | renameWorked = true; 298 | } 299 | } 300 | } 301 | } 302 | 303 | // The new test message will be placed in test1 - creating new file 304 | File newFile = new File(testDir, "test1.txt"); 305 | FileWriter writer = new FileWriter(newFile); 306 | writer.write(test); 307 | System.out.println(DatePretty.date() + "New file created"); 308 | writer.close(); 309 | } 310 | } 311 | --------------------------------------------------------------------------------