Skip to content

Commit 4fdd52a

Browse files
committed
added bulk upload example
1 parent dbd834d commit 4fdd52a

15 files changed

Lines changed: 343 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: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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+
@Override
38+
protected Object doWork(WorkArguments args, ModelMap model, HttpServletResponse response)
39+
throws Exception {
40+
var accountId = session.getAccountId();
41+
var accessToken = user.getAccessToken();
42+
43+
var jobInfo = NavigatorMethodsService.createBulkUploadJob(accountId, accessToken);
44+
session.setBulkJobId(jobInfo.getJobId());
45+
session.setBulkUploadUrls(jobInfo.getUploadUrls());
46+
47+
return "redirect:/nav003/uploadDocuments";
48+
}
49+
50+
@GetMapping("/uploadDocuments")
51+
public String getUploadDocuments(WorkArguments args, ModelMap model) throws Exception {
52+
super.onInitModel(args, model);
53+
return UPLOAD_DOCUMENTS_PAGE;
54+
}
55+
56+
@PostMapping("/uploadDocuments")
57+
public String postUploadDocuments(WorkArguments args, ModelMap model) throws Exception {
58+
if (session.getBulkUploadUrls() == null) {
59+
return "redirect:/nav003";
60+
}
61+
NavigatorMethodsService.uploadDocumentsToJob(session.getBulkUploadUrls());
62+
return "redirect:/nav003/completeUpload";
63+
}
64+
65+
@GetMapping("/completeUpload")
66+
public String getCompleteUpload(WorkArguments args, ModelMap model) throws Exception {
67+
super.onInitModel(args, model);
68+
return COMPLETE_UPLOAD_PAGE;
69+
}
70+
71+
@PostMapping("/completeUpload")
72+
public String postCompleteUpload(WorkArguments args, ModelMap model) throws Exception {
73+
var jobId = session.getBulkJobId();
74+
if (jobId == null) {
75+
return "redirect:/nav003";
76+
}
77+
var accountId = session.getAccountId();
78+
var accessToken = user.getAccessToken();
79+
80+
NavigatorMethodsService.completeBulkUploadJob(accountId, accessToken, jobId);
81+
82+
DoneExample.createDefault(getTextForCodeExampleByApiType().ExampleName)
83+
.withMessage(getTextForCodeExampleByApiType().AdditionalPage.get(1).ResultsPageText)
84+
.addToModel(model, config);
85+
86+
return DONE_EXAMPLE_PAGE;
87+
}
88+
}
Lines changed: 151 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,182 @@
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.datatype.jsr310.JavaTimeModule;
913
import org.openapitools.jackson.nullable.JsonNullableModule;
1014

15+
import java.util.List;
16+
import java.net.URI;
17+
import java.net.http.HttpClient;
18+
import java.net.http.HttpRequest;
19+
import java.net.http.HttpResponse;
20+
import java.util.ArrayList;
21+
import java.util.logging.Logger;
22+
1123
public class NavigatorMethodsService {
24+
private static final HttpClient HTTP_CLIENT = HttpClient.newHttpClient();
25+
1226
//ds-snippet-start:NavigatorJavaStep2
1327
private static IamClient createIamClient(String accessToken) {
1428
return IamClient.builder()
15-
.accessToken(accessToken)
16-
.build();
29+
.accessToken(accessToken)
30+
.build();
1731
}
1832
//ds-snippet-end:NavigatorJavaStep2
1933

2034
public static GetAgreementsListResponse getAgreements(String accountId, String accessToken) throws Exception {
2135
return createIamClient(accessToken)
22-
.navigator()
23-
.agreements()
24-
.getAgreementsList()
25-
.request(new GetAgreementsListRequest(accountId))
26-
.call();
36+
.agreementManager()
37+
.agreements()
38+
.getAgreementsList()
39+
.request(new GetAgreementsListRequest(accountId))
40+
.call();
2741
}
2842

2943
public static GetAgreementResponse getAgreement(String accountId, String accessToken, String agreementId)
30-
throws Exception {
44+
throws Exception {
3145
return createIamClient(accessToken)
32-
.navigator()
33-
.agreements()
34-
.getAgreement()
35-
.accountId(accountId)
36-
.agreementId(agreementId)
37-
.call();
46+
.agreementManager()
47+
.agreements()
48+
.getAgreement()
49+
.accountId(accountId)
50+
.agreementId(agreementId)
51+
.call();
3852
}
3953

4054
public static String serializeObjectToJson(Object data) throws Exception {
4155
var mapper = new ObjectMapper()
42-
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
43-
.registerModule(new JsonNullableModule());
56+
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
57+
.registerModule(new JavaTimeModule())
58+
.registerModule(new JsonNullableModule());
4459

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

src/main/java/com/docusign/core/model/ApiType.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public enum ApiType {
2121
new String[] { "signature", "webforms_read", "webforms_instance_read", "webforms_instance_write" }, "web"),
2222
NAVIGATOR("Navigator API", new String[] { "signature", "adm_store_unified_repo_read" }, "nav"),
2323
MAESTRO("Maestro API", new String[] { "signature", "aow_manage" }, "mae"),
24-
NOTARY("Notary API", new String[] { "signature" }, "n"),
24+
NOTARY("Notary API", new String[] { "signature", "adm_store_unified_repo_read", "document_uploader_write", "document_uploader_read" }, "n"),
2525
WORKSPACES("Workspaces API", new String[] { "signature", "dtr.company.read", "dtr.rooms.read", "dtr.rooms.write",
2626
"dtr.documents.write" }, "work");
2727

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.docusign.core.model;
2+
3+
import java.util.Objects;
4+
import java.util.List;
5+
6+
public final class BulkUploadJobInfo {
7+
private final String jobId;
8+
private final List<String> uploadUrls;
9+
10+
public BulkUploadJobInfo(String jobId, List<String> uploadUrls) {
11+
this.jobId = Objects.requireNonNull(jobId, "jobId must not be null");
12+
this.uploadUrls = Objects.requireNonNull(uploadUrls, "uploadUrls must not be null");
13+
}
14+
15+
public String getJobId() {
16+
return jobId;
17+
}
18+
19+
public List<String> getUploadUrls() {
20+
return uploadUrls;
21+
}
22+
}

src/main/java/com/docusign/core/model/Session.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,8 @@ public class Session implements Serializable {
8787
private String creatorId;
8888

8989
private String documentId;
90+
91+
private String bulkJobId;
92+
93+
private List<String> bulkUploadUrls;
9094
}

0 commit comments

Comments
 (0)