Skip to content

Commit 2a66a0a

Browse files
authored
Merge pull request #173 from docusign/bulk-upload-code-example
Bulk upload code example
2 parents ae25f10 + a29eedf commit 2a66a0a

15 files changed

Lines changed: 367 additions & 25 deletions

File tree

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
<monitor.version>1.4.0</monitor.version>
3333
<admin.version>2.0.0</admin.version>
3434
<webforms.version>2.1.0</webforms.version>
35-
<iam.version>1.0.0-beta.6</iam.version>
35+
<iam.version>1.0.0-beta.9</iam.version>
3636
<swagger-core-version>2.2.22</swagger-core-version>
3737
<jackson-version>2.17.2</jackson-version>
3838
<jersey2.version>3.1.10</jersey2.version>

src/main/java/com/docusign/controller/maestro/services/CancelWorkflowInstanceService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public static CancelWorkflowInstanceResponse CancelMaestroWorkflowInstance(
1010
String accountId,
1111
String workflowId,
1212
String instanceId) throws Exception {
13-
return client.maestro().workflowInstanceManagement()
13+
return client.workflowBuilder().workflowInstanceManagement()
1414
.cancelWorkflowInstance(accountId, workflowId, instanceId);
1515
}
1616
//ds-snippet-end:Maestro4Step3
@@ -20,7 +20,7 @@ public static GetWorkflowInstanceResponse GetWorkflowInstanceStatus(
2020
String accountId,
2121
String workflowId,
2222
String instanceId) throws Exception {
23-
return client.maestro().workflowInstanceManagement()
23+
return client.workflowBuilder().workflowInstanceManagement()
2424
.getWorkflowInstance(accountId, workflowId, instanceId);
2525
}
2626
}

src/main/java/com/docusign/controller/maestro/services/PauseWorkflowService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public static PauseNewWorkflowInstancesResponse PauseMaestroWorkflow(
99
IamClient client,
1010
String accountId,
1111
String workflowId) throws Exception {
12-
return client.maestro()
12+
return client.workflowBuilder()
1313
.workflows().pauseNewWorkflowInstances(accountId, workflowId);
1414
}
1515
//ds-snippet-end:Maestro2Step3

src/main/java/com/docusign/controller/maestro/services/ResumeWorkflowService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public static ResumePausedWorkflowResponse ResumeMaestroWorkflow(
99
IamClient client,
1010
String accountId,
1111
String workflowId) throws Exception {
12-
return client.maestro()
12+
return client.workflowBuilder()
1313
.workflows().resumePausedWorkflow(accountId, workflowId);
1414
}
1515
//ds-snippet-end:Maestro3Step3

src/main/java/com/docusign/controller/maestro/services/TriggerWorkflowService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ public static TriggerWorkflowResponse triggerWorkflowInstance(
143143
//ds-snippet-start:Maestro1Step5
144144
var triggerWorkflow = new TriggerWorkflow(instanceName, triggerInputs);
145145

146-
return client.maestro()
146+
return client.workflowBuilder()
147147
.workflows()
148148
.triggerWorkflow(accountId, workflowId, triggerWorkflow);
149149
}
@@ -153,7 +153,7 @@ public static TriggerWorkflowResponse triggerWorkflowInstance(
153153
public static GetWorkflowsListResponse getMaestroWorkflow(
154154
IamClient client,
155155
String accountId) throws Exception {
156-
return client.maestro()
156+
return client.workflowBuilder()
157157
.workflows()
158158
.getWorkflowsList(accountId, Optional.of(Status.ACTIVE), Optional.empty());
159159
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package com.docusign.controller.navigator.examples;
2+
3+
import com.docusign.DSConfiguration;
4+
import com.docusign.common.WorkArguments;
5+
import com.docusign.controller.navigator.services.NavigatorMethodsService;
6+
import com.docusign.core.model.DoneExample;
7+
import com.docusign.core.model.Session;
8+
import com.docusign.core.model.User;
9+
import org.springframework.stereotype.Controller;
10+
import org.springframework.ui.ModelMap;
11+
import org.springframework.web.bind.annotation.GetMapping;
12+
import org.springframework.web.bind.annotation.PostMapping;
13+
import org.springframework.web.bind.annotation.RequestMapping;
14+
15+
import javax.servlet.http.HttpServletResponse;
16+
17+
/**
18+
* This example demonstrates how to bulk upload documents to Navigator.
19+
* Step 1: Create a bulk upload job.
20+
* Step 2: Upload demo documents to the job's blob storage URLs.
21+
* Step 3: Mark the job as complete.
22+
*/
23+
@Controller
24+
@RequestMapping("/nav003")
25+
public class Nav003BulkUploadDocumentsController extends AbstractNavigatorController {
26+
27+
private static final String UPLOAD_DOCUMENTS_PAGE =
28+
"pages/navigator/examples/nav003UploadDocuments";
29+
30+
private static final String COMPLETE_UPLOAD_PAGE =
31+
"pages/navigator/examples/nav003CompleteUpload";
32+
33+
public Nav003BulkUploadDocumentsController(DSConfiguration config, Session session, User user) {
34+
super(config, "nav003", user, session);
35+
}
36+
37+
//ds-snippet-start:Navigator3Step2
38+
@Override
39+
protected Object doWork(WorkArguments args, ModelMap model, HttpServletResponse response)
40+
throws Exception {
41+
var accountId = session.getAccountId();
42+
var accessToken = user.getAccessToken();
43+
44+
var jobInfo = NavigatorMethodsService.createBulkUploadJob(accountId, accessToken);
45+
session.setBulkJobId(jobInfo.getJobId());
46+
session.setBulkUploadUrls(jobInfo.getUploadUrls());
47+
48+
return "redirect:/nav003/uploadDocuments";
49+
}
50+
//ds-snippet-end:Navigator3Step2
51+
52+
//ds-snippet-start:Navigator3Step3
53+
@GetMapping("/uploadDocuments")
54+
public String getUploadDocuments(WorkArguments args, ModelMap model) throws Exception {
55+
super.onInitModel(args, model);
56+
return UPLOAD_DOCUMENTS_PAGE;
57+
}
58+
59+
@PostMapping("/uploadDocuments")
60+
public String postUploadDocuments(WorkArguments args, ModelMap model) throws Exception {
61+
if (session.getBulkUploadUrls() == null) {
62+
return "redirect:/nav003";
63+
}
64+
NavigatorMethodsService.uploadDocumentsToJob(session.getBulkUploadUrls());
65+
return "redirect:/nav003/completeUpload";
66+
}
67+
//ds-snippet-end:Navigator3Step3
68+
69+
//ds-snippet-start:Navigator3Step4
70+
@GetMapping("/completeUpload")
71+
public String getCompleteUpload(WorkArguments args, ModelMap model) throws Exception {
72+
super.onInitModel(args, model);
73+
return COMPLETE_UPLOAD_PAGE;
74+
}
75+
76+
@PostMapping("/completeUpload")
77+
public String postCompleteUpload(WorkArguments args, ModelMap model) throws Exception {
78+
var jobId = session.getBulkJobId();
79+
if (jobId == null) {
80+
return "redirect:/nav003";
81+
}
82+
var accountId = session.getAccountId();
83+
var accessToken = user.getAccessToken();
84+
85+
var completeUploadResult = NavigatorMethodsService.completeBulkUploadJob(accountId, accessToken, jobId);
86+
87+
var jsonAgreement = NavigatorMethodsService.serializeObjectToJson(completeUploadResult.bulkJob().orElseThrow());
88+
DoneExample.createDefault(getTextForCodeExampleByApiType().ExampleName)
89+
.withMessage(getTextForCodeExampleByApiType().AdditionalPage.get(1).ResultsPageText)
90+
.withJsonObject(jsonAgreement)
91+
.addToModel(model, config);
92+
93+
return DONE_EXAMPLE_PAGE;
94+
//ds-snippet-end:Navigator3Step4
95+
}
96+
}
Lines changed: 184 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,215 @@
11
package com.docusign.controller.navigator.services;
22

3+
import com.docusign.core.model.BulkUploadJobInfo;
34
import com.docusign.iam.sdk.IamClient;
5+
import com.docusign.iam.sdk.models.components.CreateBulkJob;
46
import com.docusign.iam.sdk.models.operations.GetAgreementResponse;
57
import com.docusign.iam.sdk.models.operations.GetAgreementsListRequest;
68
import com.docusign.iam.sdk.models.operations.GetAgreementsListResponse;
9+
import com.docusign.iam.sdk.models.operations.UploadCompleteBulkJobResponse;
710
import com.fasterxml.jackson.annotation.JsonInclude;
811
import com.fasterxml.jackson.databind.ObjectMapper;
12+
import com.fasterxml.jackson.databind.SerializationFeature;
13+
import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
14+
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
915
import org.openapitools.jackson.nullable.JsonNullableModule;
1016

17+
import java.util.List;
18+
import java.net.URI;
19+
import java.net.http.HttpClient;
20+
import java.net.http.HttpRequest;
21+
import java.net.http.HttpResponse;
22+
import java.util.ArrayList;
23+
import java.util.logging.Logger;
24+
1125
public class NavigatorMethodsService {
26+
private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();
27+
1228
//ds-snippet-start:NavigatorJavaStep2
1329
private static IamClient createIamClient(String accessToken) {
1430
return IamClient.builder()
15-
.accessToken(accessToken)
16-
.build();
31+
.accessToken(accessToken)
32+
.build();
1733
}
1834
//ds-snippet-end:NavigatorJavaStep2
1935

2036
public static GetAgreementsListResponse getAgreements(String accountId, String accessToken) throws Exception {
2137
return createIamClient(accessToken)
22-
.navigator()
23-
.agreements()
24-
.getAgreementsList()
25-
.request(new GetAgreementsListRequest(accountId))
26-
.call();
38+
.agreementManager()
39+
.agreements()
40+
.getAgreementsList()
41+
.request(new GetAgreementsListRequest(accountId))
42+
.call();
2743
}
2844

2945
public static GetAgreementResponse getAgreement(String accountId, String accessToken, String agreementId)
30-
throws Exception {
46+
throws Exception {
3147
return createIamClient(accessToken)
32-
.navigator()
33-
.agreements()
34-
.getAgreement()
35-
.accountId(accountId)
36-
.agreementId(agreementId)
37-
.call();
48+
.agreementManager()
49+
.agreements()
50+
.getAgreement()
51+
.accountId(accountId)
52+
.agreementId(agreementId)
53+
.call();
3854
}
3955

4056
public static String serializeObjectToJson(Object data) throws Exception {
4157
var mapper = new ObjectMapper()
42-
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
43-
.registerModule(new JsonNullableModule());
58+
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
59+
.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
60+
.registerModule(new JavaTimeModule())
61+
.registerModule(new Jdk8Module())
62+
.registerModule(new JsonNullableModule());
4463

4564
return mapper.writeValueAsString(data);
4665
}
66+
67+
public static BulkUploadJobInfo createBulkUploadJob(
68+
String accountId,
69+
String accessToken
70+
) throws Exception {
71+
var client = createIamClient(accessToken);
72+
73+
var demoDocs = getAvailableDemoDocuments();
74+
if (demoDocs.isEmpty()) {
75+
throw new IllegalStateException("No demo documents found on classpath");
76+
}
77+
78+
var createJob = CreateBulkJob.builder()
79+
.jobName("Example bulk upload job")
80+
.expectedNumberOfDocs(demoDocs.size())
81+
.language("en-US")
82+
.build();
83+
84+
var createResponse = client.agreementManager().bulkJob()
85+
.createBulkUploadJob(accountId, createJob);
86+
87+
var bulkJob = createResponse.bulkJob().orElseThrow();
88+
var jobId = bulkJob.id();
89+
if (jobId == null || jobId.isBlank()) {
90+
throw new IllegalStateException("API returned a bulk job with no ID");
91+
}
92+
93+
var documents = bulkJob.embedded().orElseThrow().documents().orElseThrow();
94+
95+
var uploadUrls = new ArrayList<String>();
96+
for (var doc : documents) {
97+
var actions = doc.actions().orElse(null);
98+
var url = actions != null ? actions.uploadDocument().orElse(null) : null;
99+
uploadUrls.add(url != null ? url : "");
100+
}
101+
102+
return new BulkUploadJobInfo(jobId, uploadUrls);
103+
}
104+
105+
public static void uploadDocumentsToJob(List<String> uploadUrls) throws Exception {
106+
var demoDocs = getAvailableDemoDocuments();
107+
108+
for (int i = 0; i < demoDocs.size(); i++) {
109+
if (i >= uploadUrls.size())
110+
break;
111+
112+
var uploadUrl = uploadUrls.get(i);
113+
if (uploadUrl == null || uploadUrl.isEmpty())
114+
continue;
115+
116+
var docInfo = demoDocs.get(i);
117+
var bytes = loadClasspathResource(docInfo);
118+
if (bytes == null)
119+
continue;
120+
121+
uploadToBlobStorage(bytes, getContentType(docInfo), docInfo, uploadUrl);
122+
}
123+
}
124+
125+
public static UploadCompleteBulkJobResponse completeBulkUploadJob(
126+
String accountId,
127+
String accessToken,
128+
String jobId
129+
) throws Exception {
130+
return createIamClient(accessToken).agreementManager().bulkJob()
131+
.uploadCompleteBulkJob(accountId, jobId);
132+
}
133+
134+
private static List<String> getAvailableDemoDocuments() {
135+
String[] candidates = {
136+
"World_Wide_Corp_Battle_Plan_Trafalgar.docx",
137+
"World_Wide_Corp_lorem.pdf",
138+
"doc_1.html",
139+
"Welcome.txt",
140+
"Id.jpg",
141+
};
142+
143+
var available = new ArrayList<String>();
144+
for (var doc : candidates) {
145+
if (NavigatorMethodsService.class.getClassLoader().getResource(doc) != null) {
146+
available.add(doc);
147+
}
148+
}
149+
return available;
150+
}
151+
152+
private static String getContentType(String filename) {
153+
String extension = "";
154+
int lastDot = filename.lastIndexOf('.');
155+
156+
if (lastDot >= 0) {
157+
extension = filename.substring(lastDot).toLowerCase();
158+
}
159+
160+
switch (extension) {
161+
case ".docx": {
162+
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
163+
}
164+
case ".pdf": {
165+
return "application/pdf";
166+
}
167+
case ".html": {
168+
return "text/html";
169+
}
170+
case ".txt": {
171+
return "text/plain";
172+
}
173+
case ".jpg":
174+
case ".jpeg": {
175+
return "image/jpeg";
176+
}
177+
default: {
178+
return "application/octet-stream";
179+
}
180+
}
181+
}
182+
183+
private static byte[] loadClasspathResource(String resourcePath) {
184+
try (var stream = NavigatorMethodsService.class.getClassLoader().getResourceAsStream(resourcePath)) {
185+
if (stream == null)
186+
return null;
187+
188+
return stream.readAllBytes();
189+
} catch (Exception e) {
190+
Logger.getLogger(NavigatorMethodsService.class.getName())
191+
.warning("Could not load resource: " + resourcePath);
192+
return null;
193+
}
194+
}
195+
196+
private static void uploadToBlobStorage(
197+
byte[] bytes,
198+
String contentType,
199+
String fileName,
200+
String url
201+
) throws Exception {
202+
var request = HttpRequest.newBuilder()
203+
.uri(URI.create(url))
204+
.header("Content-Type", contentType)
205+
.header("x-ms-blob-type", "BlockBlob")
206+
.header("x-ms-meta-filename", fileName)
207+
.PUT(HttpRequest.BodyPublishers.ofByteArray(bytes))
208+
.build();
209+
var httpResponse = HTTP_CLIENT.send(request, HttpResponse.BodyHandlers.discarding());
210+
211+
if (httpResponse.statusCode() < 200 || httpResponse.statusCode() >= 300) {
212+
throw new java.io.IOException("Blob upload failed for " + fileName + ": HTTP " + httpResponse.statusCode());
213+
}
214+
}
47215
}

0 commit comments

Comments
 (0)