Package com.veeva.vault.sdk.api.workflow


package com.veeva.vault.sdk.api.workflow
This package provides interfaces to create custom actions for workflows, inititate workflow actions, and update entities within workflow processing.

Record Workflow Actions

Workflows can have custom actions that perform specific instructions. You can use a custom record workflow action to automate certain business processes on an object or document workflow. Document workflows are a type of object workflow configured on the envelope__sys object. If you are unfamiliar with object or document workflows, you should learn more in Vault Help before coding a record workflow action:

You can configure a custom action for workflows on any of the following steps:

  • Start step: Actions configured against a participant control which is invoked during start step events. This action can include logic to populate the workflow participants.
  • Task step: Actions which are automatically invoked when a workflow task reaches a specified task event. Learn more about available events in WorkflowEvent.

A record workflow action is a Java class that implements the RecordWorkflowAction interface and has the @RecordWorkflowActionInfo annotation. The @RecordWorkflowActionInfo annotation has the following elements:

  • label: Label of the action
  • object: If specified, the action is only available for object workflows associated to the specified object. If omitted, the action is available across all object workflows.
  • stepTypes: The workflow step types that this action can be configured against.

Example: Record Workflow Action on a Start Step

The following example illustrates a custom action available for configuration on the Start step of an object workflow configured for the Product object. Once configured on an object workflow's start step participant control, the action will be invoked on all start step events during workflow execution.

This example executes custom logic on the following events:

  • During the DISPLAY_PARTICIPANTS event, this action populates the workflow start dialog with two specific users.
  • During the GET_PARTICIPANTS event, this action populates the participant group for the configured participant control with two specific users.
  • During the AFTER_CREATE event, this action sends a notification to each participant.
 @RecordWorkflowActionInfo(label="Custom Approver", object="product__v", stepTypes={WorkflowStepType.START})
 public class CustomApprover implements RecordWorkflowAction {
          public void execute(RecordWorkflowActionContext context) {
             WorkflowEvent event = context.getEvent();
             WorkflowInstance workflowInstance = context.getWorkflowInstance();

             WorkflowInstanceService workflowInstanceService = ServiceLocator.locate(WorkflowInstanceService.class);
             WorkflowParticipantGroup participantGroup = context.getParticipantGroup();

             //DISPLAY_PARTICIPANTS only shows the users/groups provided by the RecordWorkflowAction. It's not officially saved until GET_PARTICIPANTS.
             //Generally, you would display and save the same users/groups.
             if (event == WorkflowEvent.DISPLAY_PARTICIPANTS || event == WorkflowEvent.GET_PARTICIPANTS) {
                 getParticipants(workflowInstanceService, participantGroup);
             } else if (event == WorkflowEvent.AFTER_CREATE) {
                 afterCreate(workflowInstance, participantGroup);
             }
         }
         private void getParticipants(WorkflowInstanceService workflowInstanceService,
                                      WorkflowParticipantGroup participantGroup) {
             Set<String> users = VaultCollections.newSet();
             users.add("1000000");
             users.add("1000001");
             WorkflowParticipantGroupUpdate participantGroupUpdate =
                     workflowInstanceService.newParticipantGroupUpdate(participantGroup)
                             .setUsers(users);
             workflowInstanceService.updateParticipantGroup(participantGroupUpdate);
         }
         private void afterCreate(WorkflowInstance workflowInstance,
                                  WorkflowParticipantGroup participantGroup) {
             NotificationService notificationService = ServiceLocator.locate(NotificationService.class);

             String processInstanceId = workflowInstance.getId();
             Set<String> participantUserIds = participantGroup.getUsers();
             String participantGroupLabel = participantGroup.getLabel();

             NotificationParameters parameters =
                     notificationService.newNotificationParameters()
                             .setRecipientsByUserIds(participantUserIds);
             String notificationText = "You were added to the participant group \"" + participantGroupLabel
                     + "\" of workflow " + processInstanceId;
             NotificationMessage message =
                     notificationService.newNotificationMessage()
                             .setSubject("A new workflow started")
                             .setMessage(notificationText)
                             .setNotificationText(notificationText);
             notificationService.send(parameters, message);
         }
    }
 
 

Example: Record Workflow Action on a Task Step

The following example illustrates an action that is available for configuration on the Task step of an object workflow configured for the Product object. Once configured on an object workflow's task step, the action will be invoked on all task events for that workflow task step during workflow execution. The code in this example handles task creation, completion dialog, completion, cancellation, and reassignment/acceptance.
  • During the TASK_AFTER_CREATE event, the action logs information for the created task instances.
  • During the TASK_BEFORE_COMPLETE_DIALOG event, the action logs information on the items in the workflow.
  • During the TASK_AFTER_COMPLETE event, the action cancels all other outstanding task instances for the current task step.
  • During the TASK_AFTER_CANCEL event, the action sends a notification to the assignees of each cancelled task instance.
  • During the TASK_AFTER_ASSIGN event, the action logs information for the tasks that have been either reassigned or accepted.
 @RecordWorkflowActionInfo(label="Approver Task Action", object="product__v", stepTypes={WorkflowStepType.TASK})
 public class ApproverTaskAction implements RecordWorkflowAction {
         public void execute(RecordWorkflowActionContext context) {
             WorkflowEvent taskEvent = context.getEvent();
             if (taskEvent == WorkflowEvent.TASK_AFTER_CREATE) {
                 handleCreate(context);
             } else if (taskEvent == WorkflowEvent.TASK_BEFORE_COMPLETE_DIALOG) {
                 handleCompleteDialog(context);
             } else if (taskEvent == WorkflowEvent.TASK_AFTER_COMPLETE) {
                 handleComplete(context);
             } else if (taskEvent == WorkflowEvent.TASK_AFTER_CANCEL) {
                 handleCancel(context);
             } else if (taskEvent == WorkflowEvent.TASK_AFTER_ASSIGN) {
                 handleAssign(context);
             }
         }
         private void handleCreate(RecordWorkflowActionContext context) {
             LogService logger = ServiceLocator.locate(LogService.class);
             RecordWorkflowActionTaskContext taskContext = context.getTaskContext();
             WorkflowTaskConfiguration taskConfiguration = taskContext.getTaskConfiguration();
             String participantGroupLabel = taskConfiguration.getParticipantGroupLabel();

             List<WorkflowTaskChange> createdTaskChanges = taskContext.getTaskChanges();
             logger.info(createdTaskChanges.size() + " tasks have been created for " +
                     "participant group " + participantGroupLabel);

             for (WorkflowTaskChange taskChange : createdTaskChanges) {
                 WorkflowTaskInstance createdTaskInstance = taskChange.getNew();
                 String taskId = createdTaskInstance.getId();
                 String assigneeId = createdTaskInstance.getAssigneeId();
                 logger.info("Task " + taskId + " has been created for user " + assigneeId);
             }
         }
         private void handleCompleteDialog(RecordWorkflowActionContext context) {
             LogService logger = ServiceLocator.locate(LogService.class);
             logger.info("[Task before complete dialog] The current workflow has the following items: ");
             List<WorkflowItem> workflowItems = context.getWorkflowItems();
             for(WorkflowItem item : workflowItems) {
                 if(item.getWorkflowItemType() == WorkflowItemType.RECORD) {
                     WorkflowItemRecord record = item.getTypedWorkflowItem(WorkflowItemRecord.class);
                     logger.info("Record: {}.{}", record.getObjectName(), record.getRecordId());
                 } else if(item.getWorkflowItemType() == WorkflowItemType.DOCUMENT) {
                     WorkflowItemDocument document = item.getTypedWorkflowItem(WorkflowItemDocument.class);
                     logger.info("Document Version: {}_{}_{}", document.getId(), document.getMajorVersion(), document.getMinorVersion());
                 }
             }
         }
         private void handleComplete(RecordWorkflowActionContext context) {
             WorkflowTaskService workflowTaskService = ServiceLocator.locate(WorkflowTaskService.class);
             WorkflowInstance workflowInstance = context.getWorkflowInstance();
             RecordWorkflowActionTaskContext taskContext = context.getTaskContext();
             WorkflowTaskConfiguration taskConfiguration = taskContext.getTaskConfiguration();
             WorkflowTaskInstance completedTaskInstance = taskContext.getTaskChanges().get(0).getNew();

             // Find all outstanding task instances (assigned or available) for the current task step
             WorkflowTaskQueryParameters queryParameters = workflowTaskService.newWorkflowTaskQueryParameters()
                     .setStatuses(WorkflowTaskStatus.ASSIGNED, WorkflowTaskStatus.AVAILABLE)
                     .setTaskConfigurations(taskConfiguration);
             List<WorkflowTaskInstance> openTaskInstancesForTask =
                     workflowTaskService.getTaskInstances(workflowInstance, queryParameters);

             // Cancel all other outstanding task instances for the current task step
             List<WorkflowTaskInstance> tasksToCancel = VaultCollections.newList();
             for (WorkflowTaskInstance taskInstance : openTaskInstancesForTask) {
                 if (!taskInstance.getId().equals(completedTaskInstance.getId())) {
                     tasksToCancel.add(taskInstance);
                 }
             }
             if (!tasksToCancel.isEmpty()) { workflowTaskService.cancel(workflowInstance, tasksToCancel); }
         }
         private void handleCancel(RecordWorkflowActionContext context) {
             RecordWorkflowActionTaskContext taskContext = context.getTaskContext();
             String taskLabel = taskContext.getTaskConfiguration().getLabel();
             List<WorkflowTaskChange> cancelledTaskChanges = taskContext.getTaskChanges();
             String workflowLabel = context.getWorkflowConfiguration().getLabel();

             NotificationService notificationService = ServiceLocator.locate(NotificationService.class);

             Set<String> recipients = VaultCollections.newSet();
             for (WorkflowTaskChange taskChange : cancelledTaskChanges) {
                 WorkflowTaskInstance cancelledTaskInstance = taskChange.getNew();
                 String assigneeId = cancelledTaskInstance.getAssigneeId();
                 recipients.add(assigneeId);
             }

             NotificationParameters parameters =
                     notificationService.newNotificationParameters()
                             .setRecipientsByUserIds(recipients);
             String notificationText = "Task \"" + taskLabel + "\" was cancelled for workflow \"" + workflowLabel + "\".";
             NotificationMessage message =
                     notificationService.newNotificationMessage()
                             .setSubject(notificationText)
                             .setMessage(notificationText)
                             .setNotificationText(notificationText);
             notificationService.send(parameters, message);
         }
         private void handleAssign(RecordWorkflowActionContext context) {
             RecordWorkflowActionTaskContext taskContext = context.getTaskContext();
             WorkflowTaskChange taskChange = taskContext.getTaskChanges().get(0);
             WorkflowTaskInstance newTaskInstance = taskChange.getNew();
             WorkflowTaskInstance oldTaskInstance = taskChange.getOld();
             WorkflowTaskStatus newTaskStatus = newTaskInstance.getStatus();
             WorkflowTaskStatus oldTaskStatus = oldTaskInstance.getStatus();

             String taskId = newTaskInstance.getId();
             String newAssigneeId = newTaskInstance.getAssigneeId();
             String oldAssigneeId = oldTaskInstance.getAssigneeId();

             LogService logger = ServiceLocator.locate(LogService.class);
             if ((oldTaskStatus == WorkflowTaskStatus.ASSIGNED) && (newTaskStatus == WorkflowTaskStatus.ASSIGNED)) {
                 logger.info("Task " + taskId + " has been reassigned from user " + oldAssigneeId + " to " +
                         "user " + newAssigneeId);
             } else if ((oldTaskStatus == WorkflowTaskStatus.AVAILABLE) && (newTaskStatus == WorkflowTaskStatus.ASSIGNED)) {
                 logger.info("Task " + taskId + " has been accepted by user " + newAssigneeId);
             }
         }
    }
 
 

Workflow Action Services

In addition to workflow actions, the interfaces in this package provide services to initiate workflow actions and workflow task actions. The services provide methods to retrieve any data necessary to execute the action, populate that data, and then execute the action.

Example: Starting a Workflow

The following is an example of a starting a workflow with the Vault Java SDK:

 // Records to start Workflow with
 String objectName = "object__c";
 String record1 = "V5K000000001001";
 String record2 = "V5K000000001002";
 List records = VaultCollections.asList(record1, record2);  // can also be list of documents if it's a document workflow

 WorkflowMetadataService workflowMetadataService = ServiceLocator.locate(WorkflowMetadataService.class);

 // 1) Get available workflows for given records
 AvailableWorkflowMetadataCollectionRequest availableWorkflowsRequest = workflowMetadataService.newAvailableWorkflowMetadataCollectionRequestBuilder()
                                                                 .withRecords(objectName, records)
                                                                 .build();

 AvailableWorkflowMetadataCollectionResponse response = workflowMetadataService.getAvailableWorkflows(availableWorkflowsRequest);

 // Get list of available workflow for the given content listing
 List availableWorkflows = response.getWorkflows();

 // Can loop over list of AvailableWorkflowMetadata to find/show all available workflows


 // 2) Get Start step details to begin workflow
 WorkflowStartMetadataRequest startMetadataRequest = workflowMetadataService.newWorkflowStartMetadataRequestBuilder()
                                                                     .withWorkflowName(workflowName)
                                                                     .withRecords(objectName, records)
                                                                     .build();

 WorkflowStartMetadataResponse startMetadataResponse = workflowMetadataService.getWorkflowStartMetadata(startMetadataRequest);

 // List of control sections in the start step
 List startStepMetadata = startMetadataResponse.getStartStepMetadataList();

 for (WorkflowStartStepMetadata metadata : startStepMetadata){
     WorkflowStartStepType metaDataType = metadata.getType();  // Type of Start control (Participant, date, etc.)

     List parameters = metadata.getParameters(); // Parameters needed to start the workflow

     for (WorkflowParameterMetadata param : parameters) {
       String paramName = param.getName();                           // used in input parameter key
             WorkflowInputValueType valueType = param.getDataType();       // used to set the value of the control
         }
     }
 }

 // 3) Start workflow with these start inputs

 String participantName = "viewer__c";
 Long userId = 100l;
 Long groupId = 500l;
 List userIds = VaultCollections.asList(userId.toString());
 List groupIds = VaultCollections.asList(groupId.toString());

 String numberInput = "number__c";
 int number = 123;

 WorkflowInstanceService workflowInstanceService = ServiceLocator.locate(WorkflowInstanceService.class);

 // Set workflow participants input
 WorkflowParticipantInputParameter workflowParticipantInputParameter = workflowInstanceService.newWorkflowParticipantInputParameterBuilder()
             .withUserIds(userIds)
             .withGroupIds(groupIds)
             .build();

 // Create Start instance request with the builder
 WorkflowStartInstanceRequest startRequest = workflowInstanceService.newWorkflowStartRequestBuilder()
             .withWorkflowName(workflowName)
             .withRecords(objectName, records)
             .withInputParameters(numberInput, number)
             .withInputParameters(participantName, workflowParticipantInputParameter)
             .build();

 // Start workflow with start request and success and error handlers
 workflowInstanceService.startWorkflow(startRequest)
             .onSuccess(startResponse -> {
                 String workflowId = startResponse.getWorkflowId();
                 // Developer provided success handling logic here
             })
             .onError(startResponse -> {
                 // Developer provided failure handling logic here
                 if (startResponse.getErrorType() == ActionErrorType.INVALID_REQUEST) {
                     throw new RuntimeException(startResponse.getMessage());
                 } else {
                     // ...
                 }
             })
             .execute();  // needed to execute the action

 

Example: Retrieving and Executing Workflow Actions

The following code provides an example of retrieving available workflow actions that can be executed on a specified workflow and provides examples of executing WorkflowActions:
@RecordActionInfo(label="Record Action with WorkflowActions", object="product__v")
 public class RecordActionWithWorkflowActions implements RecordAction {

     @Override
     public boolean isExecutable(RecordActionContext context) {
         String workflowId = "105";
         WorkflowInstanceService workflowInstanceService = ServiceLocator.locate(WorkflowInstanceService.class);
         List workflowActions = getAvailableWorkflowActions(workflowInstanceService, workflowId);
         return !workflowActions.isEmpty();
     }

     @Override
     public void execute(RecordActionContext context) {
         WorkflowInstanceService workflowInstanceService = ServiceLocator.locate(WorkflowInstanceService.class);
         String workflowId = "105";

         // provide logic to determine workflowAction
         WorkflowAction workflowAction = WorkflowAction.ADD_PARTICIPANTS;

         switch (workflowAction) {
             case ADD_PARTICIPANTS:
                 addParticipants(workflowInstanceService, workflowId);
                 break;
             case CANCEL_WORKFLOW:
                 cancelWorkflow(workflowInstanceService, workflowId);
                 break;
             case REMOVE_ITEMS:
                 removeItems(workflowInstanceService, workflowId);
                 break;
             case REPLACE_WORKFLOW_OWNER:
                 replaceWorkflowOwner(workflowInstanceService, workflowId);
                 break;
             case UPDATE_WORKFLOW_DUE_DATE:
                 updateWorkflowDueDate(workflowInstanceService, workflowId);
                 break;
             default:
                 break;
         }
     }

     private List getAvailableWorkflowActions(WorkflowInstanceService workflowInstanceService, String workflowId) {
         // Retrieving all available workflow actions that can be executed on a specific workflow
         WorkflowInstanceRequest workflowInstanceRequest = workflowInstanceService.newWorkflowInstanceRequestBuilder()
             .withWorkflowId(workflowId).build();
         AvailableWorkflowInstanceActionsResponse workflowActionsResponse = workflowInstanceService.getAvailableWorkflowInstanceActions(workflowInstanceRequest);
         return workflowActionsResponse.getWorkflowActions();
     }

     private void addParticipants(WorkflowInstanceService workflowInstanceService,
                                  String workflowId) {
         String groupName1 = "part_approvers__c";
         List newUsers1 = VaultCollections.asList("100001", "100002");
         List newGroups1 = VaultCollections.asList("13500001");
         String groupName2 = "part_reviewers__c";
         List newUsers2 = VaultCollections.asList("100003");

         WorkflowParticipantGroupInputParameter participantGroupParameter1 = workflowInstanceService.newWorkflowParticipantGroupInputParameterBuilder()
             .withParticipantGroupName(groupName1)
             .withUserIds(newUsers1)
             .withGroupIds(newGroups1).build();

         WorkflowParticipantGroupInputParameter participantGroupParameter2 = workflowInstanceService.newWorkflowParticipantGroupInputParameterBuilder()
             .withParticipantGroupName(groupName2)
             .withUserIds(newUsers2).build();

         WorkflowAddParticipantsRequest addRequest = workflowInstanceService.newWorkflowAddParticipantsRequestBuilder()
             .withWorkflowId(workflowId)
             .withInputParameters(VaultCollections.asList(participantGroupParameter1, participantGroupParameter2)).build();

         workflowInstanceService.addParticipants(addRequest)
             .onSuccess(addResponse -> {
                 String resultWorkflowId = addResponse.getWorkflowId();
                 // Developer provided success handling logic here
             })
             .onError(addResponse -> {
                 // Developer provided failure handling logic here
                 if (addResponse.getErrorType() == ActionErrorType.INVALID_REQUEST) {
                     throw new RuntimeException(addResponse.getMessage());
                 } else {
                     // ...
                 }
             })
             .execute(); // needed to execute the action
     }

     private void cancelWorkflow(WorkflowInstanceService workflowInstanceService, String workflowId) {
         WorkflowCancelRequest request = workflowInstanceService.newWorkflowCancelRequestBuilder()
             .withWorkflowId(workflowId)
             .withComment("This workflow was cancelled from ... for this reason...")
             .build();
         LogService logger = ServiceLocator.locate(LogService.class);
         workflowInstanceService.cancelWorkflow(request)
             .onSuccess(cancelResponse -> logger.info("Workflow [{}] successfully cancelled", workflowId))
             .onError(cancelResponse -> {
                 // Developer provided failure handling logic here
                 if (cancelResponse.getErrorType() == ActionErrorType.INVALID_REQUEST) {
                     throw new RuntimeException(cancelResponse.getMessage());
                 } else {
                     // ...
                 }
             }).execute();
     }

     private void removeItems(WorkflowInstanceService workflowInstanceService, String workflowId) {
         // e.g. get all workflow items and filter to desired items
         // perhaps by running vql to filter to approved verdicts completed by user1 on task1.
         WorkflowItemsResponse workflowItemsResponse = workflowInstanceService.getWorkflowItems(workflowId);
         List workflowItems = workflowItemsResponse.getWorkflowItems();

         String objectName = null;
         List recordIds = VaultCollections.newList();
         List documentIds = VaultCollections.newList();
         for (WorkflowItem item: workflowItems.subList(0, 2)) {
             if (item.getWorkflowItemType() == WorkflowItemType.DOCUMENT) {
                 documentIds.add(item.getTypedWorkflowItem(WorkflowItemDocument.class).getId());
             } else {
                 WorkflowItemRecord itemRecord = item.getTypedWorkflowItem(WorkflowItemRecord.class);
                 objectName = itemRecord.getObjectName();
                 recordIds.add(itemRecord.getRecordId());
             }
         }

         WorkflowRemoveItemsRequest.Builder requestBuilder = workflowInstanceService.newWorkflowRemoveItemsRequestBuilder()
             .withWorkflowId(workflowId);
         if (objectName != null) {
             requestBuilder.withRecords(objectName, recordIds);
         } else {
             requestBuilder.withDocumentIds(documentIds);
         }

         workflowInstanceService.removeItems(requestBuilder.build())
             .onSuccess(removeResponse -> {
                 LogService logger = ServiceLocator.locate(LogService.class);
                 String resultWorkflowId = removeResponse.getWorkflowId();
                 logger.info("Successfully removed %s from workflow [%s]", removeResponse.getRemovedItems(), resultWorkflowId);
             })
             .onError(removeResponse -> {
                 ActionErrorType wfErrorType = removeResponse.getErrorType();
                 // Developer provided failure handling logic here
                 }
             )
             .execute();
     }

     private void replaceWorkflowOwner(WorkflowInstanceService workflowInstanceService, String workflowId) {
         WorkflowReplaceOwnerRequest request = workflowInstanceService.newWorkflowReplaceOwnerRequestBuilder()
             .withWorkflowId(workflowId)
             .withNewOwnerId("100001").build();
         workflowInstanceService.replaceWorkflowOwner(request)
             .onSuccess(replaceResponse -> {
                 String resultWorkflowId = replaceResponse.getWorkflowId();
                 // ...
             }).onError(replaceResponse -> {
                 ActionErrorType wfErrorType = replaceResponse.getErrorType();
                 // ...
             })
             .execute();
     }

     private void updateWorkflowDueDate(WorkflowInstanceService workflowInstanceService, String workflowId) {
         int year = 2023;
         int month = 12;
         int day = 31;
         ZonedDateTime newDueDate = ZonedDateTime.of(year, month, day, 0, 0, 0, 0, ZoneId.of("UTC"));
         WorkflowUpdateDueDateRequest request = workflowInstanceService.newWorkflowUpdateDueDateRequestBuilder()
             .withWorkflowId(workflowId)
             .withDueDate(newDueDate)
             .build();
         workflowInstanceService.updateWorkflowDueDate(request)
             .onSuccess(updateDueDateResponse -> {
                 String resultWorkflowId = updateDueDateResponse.getWorkflowId();
                 // ...
             }).onError(updateDueDateResponse -> {
                 ActionErrorType wfErrorType = updateDueDateResponse.getErrorType();
                 // ...
             })
             .execute();
     }
 }
 }