Share:

Developers » Exception Handling Settings » Exception Handling Example

PREVIOUS  |  NEXT

Exception Handling Example

Quick Links


This example includes three steps for creating an Exception Handling case. We also provide code examples for Implemented scripts that are called from actions in the Exception Handler workflow. See Exception Handling Scripts for more information on the types of exception handling scripts.

In this example, we will demonstrate how to handle possible exceptions during a task that includes generating new licenses. We create license objects through an integration with the TrueProducer application and in Step 1, we call the asynchronous task to process the licenses. Workflow automatically populates all relevant parameters about the case into the exceptionContext object so if an exception occurs, the object will be used by the Exception Handling mechanism.

In Step 2, we explain how to handle the exception. We first call the newException method, and Workflow populates attributes into a new script exception object. Finally, we pass the attributes and parameters from the exceptionContext object to the third script, which creates the new Exception Handler case, as seen in Step 3 of this example.

Then assignees of the Exception Handler case should examine the information collected and attempt to correct the issue. For example, if data was missing from one of the license objects from TrueProducer, the missing object may cause the exception. Using the information gathered in the Exception Handler case, assignees could attempt to populate the object with the missing data and then perform one of the three available actions: Retry, Continue - Skip, or Mark as Failed.

Step 1: Exception Handling - 1 - Prepare Entities Script

[Back to top]

In this step, we execute an action that calls an asynchronous task to process the licenses, then we put the license information on the map to pass to another asynchronous task which performs subsequent actions. 

Whenever you call an asynchronous script, we populate all relevant parameters about the case into the exceptionContext object. If an exception occurs, this object will be used by the Exception Handling mechanism.

We have already created a workflow named New Producer that includes the statuses and actions as shown in the image below:



The Create Licences action shown above uses the Script Action type and has an attached Form Event script, named Exception Handling - 1 - Prepare Entities.

The script includes the following code:

// collect data displayed on the form
def tpManager = resp.loadLibrary("trueproducer");  
Map<String, Object> licences = new HashMap<String, Object>(); 

// creating 3 licences (just like they are choosen on the form)
for (i in 1..3) {   	
     def licence = tpManager.createLicense();
     licence.setLicenseNumber(i.toString());
     licence.setStartDate(resp.newDate('05/19/2014').getDate());
     licences.put(licence.getLicenseNumber(),licence);
} Map<String, Object> params = new HashMap<String, Object>(); ​params.put("licences", licences); resp.asyncTask.execute("eh_2_process_entities", params);

In the code above, we manually create three licenses as seen in lines 6 - 11, but you can modify the script to use data that end users enter on a form. You can also see where we call the TrueProducer library in line 2.

After the license information is created, we put it on the map to pass to another asynchronous task that will perform subsequent actions. 

You can see the following code on line 15 that calls the Exception Handling - 2 - Process Entities asynchronous task script type:

resp.asyncTask.execute("eh_2_process_entities", params)

where:

  • "eh_2_process_entities" is the system ID of the asynchronous task script type that we are calling.
  • params: The list of objects, or licenses that we are passing from the map.

Each time an asynchronous task is called, Workflow populates the exceptionContext object in the background. That information is then displayed in the Exception Handler caseWe include the: 

  • Case where errors occurred.
  • User who executed the script
  • Script and action that produced the exception

Important: Exception handling only works with asynchronous tasks and does not work with try-catch blocks in Form Event or Workflow scripts. We only prepare the exceptionContext method when you call an asynchronous task.  

Step 2: Exception Handling - 2 - Process Entities Script

[Back to top]

In Step 1 above, we collected licenses and passed them to the Exception Handling - 2 - Process Entities asynchronous task script. In this step, we will handle the exception. We call the newException method, then Workflow creates a new script exception attribute of the exceptionContext object in the background and populates its attributes with the values we pass, along with the values from the exceptionContext object that were initially collected. We will subsequently pass the parameters to the third script which will create the new Exception Handling case. 

For Step 2, we use the following code:

/* although you do not see currentCaseSystemId in the Script Editor's reference tools in the "Context Info" section,
it is present in the context. currentCaseSystemId and currentUserSystemId are also present in the context  
with the objects that you passed through the params argument in the asyncTask's execute method.*/

def sourceCase = resp.getCase(currentCaseSystemId);
out << currentCaseSystemId;

def exceptions = [];
def failedObjects = [];

def allSuccessful = true;

def narrative = "Operations with these objects failed: \n\n ";

// objects are available since they are passed to asyncTask's execute method in params argument
for(licence in licences.values()) {
    try {
        // perform some operations with the licence
      	def aa = 1/0; // cause an exception to be caught below
    } catch (Exception e) {
        allSuccessful = false;
        narrative = narrative + ' ' + licence.getLicenseNumber();
      	failedObjects.add(licence);
      
        // 1st option: create an exception manually

        // 2nd option: use resp.exception.newException(...)
        
     	def key = licence.getLicenseNumber();
        def message = e.getMessage();
        def description = "Something's wrong! SOS!";
        def stackTrace = e.getStackTrace().toString();
        def severity = "SEVERE";
      
        def exception = resp.exception.newException(key, message, description, stackTrace, severity);
      
        // optional if you want additional information to process in the exception handling script
        exception.setOriginalException(e);
        exceptions.add(exception);
    }
}

if (!allSuccessful) {
  sourceCase.setDescription(sourceCase.getDescription() + '<br />123<br />');
    // optional: add some custom parameters
    Map<String, Object> params = new HashMap<String, Object>();
    params.put("producer", sourceCase.getCustomFieldValue('producer_first_name') + ' ' +   	
                  sourceCase.getCustomFieldValue('producer_last_name') + ' [' + 
                  sourceCase.getCustomFieldValue('producer_id') + ']');
    // optional: pass the custom parameters for automatic creation of exception handling case
    // params.put("exceptionHandling.caseType.systemId", "customCaseTypeForHandleExceptions");
    // params.put("exceptionHandling.cf.script.retry.value", "myCustomRetryScriptImpl");

    resp.exception.handleException("eh_3_handle_exception", narrative, exceptions, failedObjects, params);
  
} else {
    // all operations completed successfully.
    // move the case to a next status, update some custom field etc.
    sourceCase.setDescription(sourceCase.getDescription() + '<br />23>br />');
    def producerCreatedStatus = resp.getStatus('producer_created');
    sourceCase.setStatus(producerCreatedStatus);
}
resp.update(sourceCase);
 

Once we call the newException method, Workflow creates a new script exception object in the background and populates its attributes with the values we pass.

See the explanations below for the parameters of the object, as seen in code line 35 above: 

def exception = resp.exception.newException(key, message, description, stackTrace, severity)

  • Key: The unique identifier of the object, or license as in this example. 
  • Message: Exception message. 
  • Description: Brief description of the exception.
  • stackTrace: List of the methods called in the script when an exception occurs.
  • Severity: Administrators define the level of severity.

See the explanations below for the parameters in code line 54 above: 

resp.exception.handleException("eh_3_handle_exception", narrative, exceptions, failedObjects, params)

  • "eh_3_handle_exception": System ID of the Exception Handling script type that will create the Exception Handler case. 
  • narrative: A brief description of the exception.
  • exceptions: List of exceptions.
  • failedObjects: List of failed objects.
  • params: A map of custom parameters that we will pass with the exception handling script.

Step 3: Exception Handling - 3 - Handle Exception

[Back to top]

In this step, we demonstrate how the Exception Handler case is created. From the asynchronous task that threw the exception, we pass the information for which objects failed into the failedObject field of the Exception Handler case, and we also pass all relevant information about the errors. Once the case is created, assignees can review the case details to determine which objects had errors and why the errors occurred. Finally, they can attempt to correct the issue.

With the code in line 54 in Step 2, we call the Exception Handling script type, named the Exception Handling - 3 - Handle Exception, which uses the following code:

def sourceCase = resp.getCase(exceptionContext.getCaseSystemId());
// creates exception handling case automatically based on passed exceptionContext object
def ehCase = resp.exception.newExceptionHandlingCase(exceptionContext);
// optional: move the source case to a status that will inform users that the issue is being resolved
def fixingBrokenLicencesStatus = resp.getStatus('fixing_broken_licences');
sourceCase.setStatus(fixingBrokenLicencesStatus);
resp.update(sourceCase);


We have already passed the exceptionContext to the script, and now with the following code, as also seen in code line 3 above, we create the Exception Handler case:

def ehCase = resp.exception.newExceptionHandlingCase(exceptionContext);

To view a complete list of exceptionContext objects, go to Setup > Development > Scripts. Open an Exception Handling script type and then expand the Context Info section in the Reference Tools.

Note: In the Context Info section of the Script Editor's reference tools, we only display the exceptionContext object for the Exception Handling script type. 


In summary, we have described an important line in each of the steps above:

  • This line from the second step creates a new exception object, and populates it with the defined parameters: 

def exception = resp.exception.newException(key, message, description, stackTrace, severity);

  • This line from the second step handles the exception, calls the third script, and passes the parameters: 

resp.exception.handleException("eh_3_handle_exception", narrative, exceptions, failedObjects, params);

  • This line from the third step creates a new Exception Handler case based on the received exceptionContext:

resp.execution.newExceptionHandlingCase(exceptionContext); 

Using Implemented Scripts

[Back to top]

The Exception Handler case type provides the following actions which have attached scripts: Retry, Continue - Skip, and Mark as Failed. These scripts exist in all tenants by default and each script calls a corresponding implemented script that performs the task. You need to customize implemented scripts with custom code for its specific use. The table below provides examples of the relationships between the actions, attached scripts, and implemented scripts with the resulting task:

Implemented scripts are attached to actions in the Exception Handler workflow, such as in the following examples:

ActionAttached Workflow ScriptWorkflow Script Calls this Implemented ScriptImplemented Script Performs this Task 
RetryException Handling - RetryException Handling - Retry - ImplRetry the process using the same failed objects. 
Continue - Skip
Exception Handling - Continue - SkipException Handling - Continue - Skip - ImplSkip failed objects and continue with the process.
Mark as FailedException Handling - Process FailedException Handling - Process Failed - Impl Render the entire process as failed because of the failed objects. 


See Exception Handling Scripts for more information.

See the comments in the code windows below for more information about the purpose of the related code lines. 


Exception Handling - Continue - Skip - Impl script code:

def exceptionCase = currentCase;
def sourceCase = resp.getCase(exceptionCase.getCustomFieldValue('exception_cause_case'));

/* move the source case to a status that will indicate the issue is resolved
   some failed objects are ignored and the process can continue even if they are broken*/
def completedStatus = resp.getStatus('producer_created');
sourceCase.setStatus(completedStatus);
resp.update(sourceCase);

// move the exception handling case to status "Resolved"
def resolvedStatus = resp.getStatus('resolved');
exceptionCase.setStatus(resolvedStatus);
resp.update(exceptionCase); 


Exception Handling - Process Failed - Impl script code:

def exceptionCase = currentCase;
def sourceCase = resp.getCase(exceptionCase.getCustomFieldValue('exception_cause_case'));

// move the source case to a status that will indicate that the process failed due to broken objects
def failedStatus = resp.getStatus('registration_failed');
sourceCase.setStatus(failedStatus);
resp.update(sourceCase);

// move the exception handling case to status "Resolved" or another status
def resolvedStatus = resp.getStatus('resolved');
exceptionCase.setStatus(resolvedStatus);
resp.update(exceptionCase);
// optional: populate a custom field that stores information about the issue not successfully resolving


Exception Handling - Retry - Impl script code:

def exceptionCase = currentCase;
def sourceCase = resp.getCase(exceptionCase.getCustomFieldValue('exception_cause_case'));
def myObjects = exceptionCase.getCustomFieldValue('exception_cause_objects');

def allSuccessful = true;

for (myObject in myObjects) {
  try {
    // retry the operations with myObject
    def a = 5/5; // the operation does not cause an exception
  } catch (Exception e) {
    allSuccessful = false;
  }
}
         
if (allSuccessful) {
    // optional: move the source case to the next status which will indicate that the issues are resolved
    def completedStatus = resp.getStatus('producer_created');
    sourceCase.setStatus(completedStatus);
    resp.update(sourceCase);
  
    // move exception handling case to status "Resolved"
    def resolvedStatus = resp.getStatus('resolved');
    exceptionCase.setStatus(resolvedStatus);
    resp.update(exceptionCase);
}