Skip to main content

How to Handle Exceptions

Overview

Exception handling in Java handles runtime errors so that the regular flow of the application can be continued with minimal interruption. Exception handling is a mechanism to handle native Java runtime errors such as

  • ClassNotFoundException
  • IOException
  • SQLException

Exception handling can also include Enactor exceptions such as

  • ApplicationProcessException
  • DatabaseUnavailableException
  • UIProcessException

Exceptions are a standard component of the Java language. Enactor's toolkit allows you integrate seamlessly with Java exceptions. This tutorial will show how to catch and handle exceptions whilst inside an application process.

When an exception occurs, an object is created. This object is called the Exception object and it contains information about the exception, such as the name and description of the exception and the state of the program when the exception occurred.

Regardless of the type of the exception, whether native Java, Enactor or custom, the exception object is made available in the application process where the exception is caught.

  • Time to Complete: 30 minutes

What You Will Learn

  • how to catch and handle an exception within an application process
  • how to handle exceptions thrown from Java actions
  • how to handle exceptions thrown from sub-processes
  • best practices for catching and handling exceptions

Pre-requisites

  • Knowledge of Java exceptions
  • Knowledge on how to write Enactor Application Processes

Instructions

Catching Exceptions

Exceptions can be thrown from

  • Actions
  • Processes
  • States

When an exception is thrown from a state, action or sub-process, it can be potentially caught within the process that is currently being executed. If the exception is not caught, just as in a Java application, it will propagate up until it is caught. It is therefore important to consider exception handling in every process that is written. Should exceptions be handled in this process? Or is it safe if they are not handled and are thrown upwards?

In the case of Processes and States, a link from an event can be defined as an exception link. In the case of Actions, an outcome link can be defined as an exception.

In order to define a link as an exception link, the name of the outcome or event must be defined as Exception. The link will change to a purple colour and will be separate from all the existing outcomes or events.

The screenshot below shows an exception link from the Call Process action called StartupExtension. If an exception is thrown from the called process, the execution flow will continue along the exception link.

Exception Properties

Exceptions can be caught in a similar way from States and at the Process level. Exceptions caught from a State only captures exceptions thrown from actions and process calls while the process is in that state. Exceptions caught at the process level will catch any exceptions not caught from actions or states. The order or precedence of exception handling, from lowest to highest level is

  • Actions
  • States
  • Process

Handling Exceptions

The example above shows basic exception handling - if an exception is thrown, the execution of the process will continue. In most cases it is prudent to log the exception as it is being caught, in order to output the error.

The example below shows an exception caught at the process level. A purple exception link has been added at the process level. When an exception is caught, the exception details will be logged to the application log and will also display an error message to the user. When the user has acknowledged the message the process will end with a fail outcome.

Process Exception and Log

Exercises

This exercise is designed to load a Product as soon as the POS starts up, which could then be used to drive some behaviour. As part of loading the product, any exceptions should be handled.

The steps in this exercise are firstly to create an application process that implements an extension point at POS start up. The extension process will

  1. Create a Product key.
  2. Populate the Product key with a Product ID that does not exist.
  3. Attempt to load the Product.
  4. Catch and handle the exception thrown from the LoadEntityAction.

Create an application process with the ID Pos/LoadProductAtStartup in the TrainingPos project. Allow the wizard to register the process, or register the process manually in the Packages.xml file:

<core:packageProcess>
<core:processId>Pos/LoadProductAtStartup</core:processId>
<core:name>Load Product At Startup</core:name>
<core:version>1.0</core:version>
</core:packageProcess>

Additionally in the same Packages file, register this new application process as an extension implementation. The extension point ID is StartupExtension and can be found in the process Pos/StartUp.

<core:packageExtension>
<core:extensionId>StartupExtension</core:extensionId>
<core:extensionPoint>StartupExtension</core:extensionPoint>
<core:applyBeforePackages/>
<core:extensionOverrides/>
<core:extensionType>Process</core:extensionType>
<core:extensionUrl>Pos/LoadProductAtStartup</core:extensionUrl>
</core:packageExtension>

In the new LoadProductAtStartup process, add a starting State and three actions to

  1. Create a Product Key
  2. Populate the Product Key with an ID known not to present in the database
  3. Load the Product

The three actions can be manually created, or can be copied and pasted from the process Pos/ProductEnquiry/LookupColourAndSize.

From the Success outcome of the LoadProduct action, end the process with Success.

The process should look similar to the screenshot below.

LoadProductAtStartup process snapshot 1

A copy of the process at this state is here (rename before importing to Eclipse): LoadProductAtStartup snapshot 1

Enable process debug in enactor-pos.log.properties for POS processes.

# Process debug
com.enactor.coreUI.processes.UIProcess.level=LOG_DEBUG
com.enactor.coreUI.states.UIState.level=LOG_DEBUG
com.enactor.coreUI.actions.UIActionExecutor.level=LOG_DEBUG

Process.Pos.StartUp.level=LOG_DEBUG
Process.Pos.LoadProductAtStartup.level=LOG_DEBUG

Without adding any exception handling, run the POS. Additional debug logging will be output, including for the new process LoadProductAtStartup. An extract of the log containing the relevant lines are shown in full below, but of particular interest are

  1. The new process starts
DEBUG Process.Pos.LoadProductAtStartup - Process Inputs are []
  1. The new process ends with an exception (as expected)
DEBUG Process.Pos.LoadProductAtStartup - Process Outcome is Exception
  1. The StartUp process handles the exception and continues, as discussed above
DEBUG Process.Pos.StartUp -  > Action Inputs  for 'WaitForSignOn' are
Full log extract
2023-08-03 14:41:06.671 [AWT-EventQueue-0] DEBUG Process.Pos.StartUp -  > Action Inputs  for 'StartupExtension' are [ enactor.coreUI.UserLocale='com.enactor.core.localisation.Locale@1b8b6872', enactor.mfc.Device='com.enactor.mfc.device.Device@140a6811', enactor.mfc.BaseCurrency='com.enactor.mfc.currency.Currency@6d83865e', enactor.mfc.Location='com.enactor.mfc.location.Store@5c5bbdfa', enactor.coreUI.ExtensionPointId='StartupExtension', enactor.mfc.PosTerminal='com.enactor.mfc.posTerminal.POSTerminal@7f1620c1' ]:Execute Action:'StartupExtension' State:'StartPeripheralsPleaseWait' Process:'Pos/StartUp'
SwingView:Not thread safe (current thread:152) making prompt using wrapper. Prompt URL:/DummyPrompt Process:NextGen/Pos/Monitor/UserTaskMonitor State:ListeningState
2023-08-03 14:41:06.697 [AWT-EventQueue-0] DEBUG Process.Pos.StartUp - < Action Outputs for 'StartupExtension' are []:Execute Action:'StartupExtension' State:'StartPeripheralsPleaseWait' Process:'Pos/StartUp'
2023-08-03 14:41:06.697 [AWT-EventQueue-0] DEBUG Process.Pos.StartUp - < Action Outcome for 'StartupExtension' is 'enactor.action.ExecuteProcess':Execute Action:'StartupExtension' State:'StartPeripheralsPleaseWait' Process:'Pos/StartUp'
2023-08-03 14:41:06.706 [main-Pos/Monitor/UserTaskMonitor-1] DEBUG com.enactor.coreUI.swing.SwingView - Event:'com.enactor.coreUI.swing.SwingView$AsyncGetPrompt' is not on the main dispatch thread.:Execute Action:'' State:'ListeningState' Process:'NextGen/Pos/Monitor/UserTaskMonitor'
2023-08-03 14:41:06.779 [AWT-EventQueue-0] DEBUG Process.Pos.LoadProductAtStartup - Process Inputs are []:Execute Action:'' State:'-process init-' Process:'Pos/LoadProductAtStartup'
2023-08-03 14:41:06.786 [AWT-EventQueue-0] DEBUG Process.Pos.LoadProductAtStartup - Process Started:Execute Action:'' State:'-process init-' Process:'Pos/LoadProductAtStartup'
2023-08-03 14:41:06.788 [AWT-EventQueue-0] DEBUG Process.Pos.LoadProductAtStartup - Internal variables for Process are []
2023-08-03 14:41:06.788 [AWT-EventQueue-0] DEBUG Process.Pos.LoadProductAtStartup - State Inputs for 'State' are [ ]
2023-08-03 14:41:06.788 [AWT-EventQueue-0] DEBUG Process.Pos.LoadProductAtStartup - > Process State Data is []:Execute Action:'CreateProductKey' State:'State' Process:'Pos/LoadProductAtStartup'
2023-08-03 14:41:06.788 [AWT-EventQueue-0] DEBUG Process.Pos.LoadProductAtStartup - > State Data for 'State' is []:Execute Action:'CreateProductKey' State:'State' Process:'Pos/LoadProductAtStartup'
2023-08-03 14:41:06.788 [AWT-EventQueue-0] DEBUG Process.Pos.LoadProductAtStartup - > Action Inputs for 'CreateProductKey' are [ enactor.coreUI.EntityNamespace='http://www.enactor.com/retail', enactor.coreUI.EntityName='product' ]:Execute Action:'CreateProductKey' State:'State' Process:'Pos/LoadProductAtStartup'
2023-08-03 14:41:06.791 [AWT-EventQueue-0] DEBUG Process.Pos.LoadProductAtStartup - < Action Outputs for 'CreateProductKey' are [ enactor.coreUI.Key='productId=;' ]:Execute Action:'CreateProductKey' State:'State' Process:'Pos/LoadProductAtStartup'
2023-08-03 14:41:06.791 [AWT-EventQueue-0] DEBUG Process.Pos.LoadProductAtStartup - < Action Outcome for 'CreateProductKey' is 'Success':Execute Action:'CreateProductKey' State:'State' Process:'Pos/LoadProductAtStartup'
2023-08-03 14:41:06.791 [AWT-EventQueue-0] DEBUG Process.Pos.LoadProductAtStartup - > Process State Data is []:Execute Action:'InitialiseProductKey' State:'State' Process:'Pos/LoadProductAtStartup'
2023-08-03 14:41:06.791 [AWT-EventQueue-0] DEBUG Process.Pos.LoadProductAtStartup - > State Data for 'State' is [ enactor.coreUI.CurrentOutcome='Success', enactor.coreUI.CurrentExecutionTime='3', enactor.mfc.ProductKey='productId=;' ]:Execute Action:'InitialiseProductKey' State:'State' Process:'Pos/LoadProductAtStartup'
2023-08-03 14:41:06.791 [AWT-EventQueue-0] DEBUG Process.Pos.LoadProductAtStartup - > Action Inputs for 'InitialiseProductKey' are [ enactor.mfc.ProductKey='productId=;', enactor.mfc.ProductId='GHENGIS_KHAN' ]:Execute Action:'InitialiseProductKey' State:'State' Process:'Pos/LoadProductAtStartup'
2023-08-03 14:41:06.791 [AWT-EventQueue-0] DEBUG Process.Pos.LoadProductAtStartup - < Action Outputs for 'InitialiseProductKey' are [ enactor.mfc.ProductKey='productId=GHENGIS_KHAN;' ]:Execute Action:'InitialiseProductKey' State:'State' Process:'Pos/LoadProductAtStartup'
2023-08-03 14:41:06.791 [AWT-EventQueue-0] DEBUG Process.Pos.LoadProductAtStartup - < Action Outcome for 'InitialiseProductKey' is 'Success':Execute Action:'InitialiseProductKey' State:'State' Process:'Pos/LoadProductAtStartup'
2023-08-03 14:41:06.799 [AWT-EventQueue-0] DEBUG Process.Pos.LoadProductAtStartup - > Process State Data is []:Execute Action:'LoadProduct' State:'State' Process:'Pos/LoadProductAtStartup'
2023-08-03 14:41:06.799 [AWT-EventQueue-0] DEBUG Process.Pos.LoadProductAtStartup - > State Data for 'State' is [ enactor.coreUI.CurrentOutcome='Success', enactor.coreUI.CurrentExecutionTime='0', enactor.mfc.ProductKey='productId=GHENGIS_KHAN;' ]:Execute Action:'LoadProduct' State:'State' Process:'Pos/LoadProductAtStartup'
2023-08-03 14:41:06.800 [AWT-EventQueue-0] DEBUG Process.Pos.LoadProductAtStartup - > Action Inputs for 'LoadProduct' are [ enactor.coreUI.Key='productId=GHENGIS_KHAN;' ]:Execute Action:'LoadProduct' State:'State' Process:'Pos/LoadProductAtStartup'
2023-08-03 14:41:06.883 [AWT-EventQueue-0] DEBUG Process.Pos.LoadProductAtStartup - State State Data for 'State' are [ enactor.coreUI.CurrentOutcome='Success', enactor.coreUI.CurrentExecutionTime='0', enactor.mfc.ProductKey='productId=GHENGIS_KHAN;', Exception='com.enactor.coreUI.processes.LocalizedUIProcessException: No item with the specified key exists. Entity product key productId=GHENGIS_KHAN;. ErrorCode:ItemDoesNotExist (Caused by com.enactor.core.database.NoSuchRecordException: productId=GHENGIS_KHAN; ErrorCode:ItemDoesNotExist Key:productId=GHENGIS_KHAN;)' ]
2023-08-03 14:41:06.884 [AWT-EventQueue-0] DEBUG Process.Pos.LoadProductAtStartup - State Outputs for 'State' are []
2023-08-03 14:41:06.884 [AWT-EventQueue-0] DEBUG Process.Pos.LoadProductAtStartup - UIProcess.end():Pos/LoadProductAtStartup User Interface Time=0 Elapsed time=105 Total time=105
2023-08-03 14:41:06.884 [AWT-EventQueue-0] DEBUG Process.Pos.LoadProductAtStartup - Process Outputs are []
2023-08-03 14:41:06.884 [AWT-EventQueue-0] DEBUG Process.Pos.LoadProductAtStartup - Process Outcome is Exception
2023-08-03 14:41:06.884 [AWT-EventQueue-0] DEBUG Process.Pos.LoadProductAtStartup - Process Ended
2023-08-03 14:41:06.888 [AWT-EventQueue-0] DEBUG Process.Pos.StartUp - > Process State Data is [ enactor.mfc.DeviceStatus='com.enactor.mfc.device.status.PosTerminalStatus@1ed21bb6', enactor.coreUI.LayoutURL='Pos/FullScreenTraditionalPosTemplate', enactor.mfc.PosTerminalKey='deviceId=pos1@0001.enactor;', enactor.coreUI.UserLocale='com.enactor.core.localisation.Locale@1b8b6872', enactor.mfc.Device='com.enactor.mfc.device.Device@140a6811', enactor.mfc.DeviceKey='deviceId=pos1@0001.enactor;', enactor.mfc.Location='com.enactor.mfc.location.Store@5c5bbdfa', enactor.mfc.BaseCurrency='com.enactor.mfc.currency.Currency@6d83865e', enactor.mfc.UseTestPeripherals='false', enactor.mfc.PosTerminal='com.enactor.mfc.posTerminal.POSTerminal@7f1620c1' ]:Execute Action:'WaitForSignOn' State:'StartPeripheralsPleaseWait' Process:'Pos/StartUp'
2023-08-03 14:41:06.888 [AWT-EventQueue-0] DEBUG Process.Pos.StartUp - > State Data for 'StartPeripheralsPleaseWait' is [ enactor.coreUI.CurrentOutcome='Success', enactor.coreUI.CurrentExecutionTime='26', Exception='com.enactor.coreUI.processes.LocalizedUIProcessException: No item with the specified key exists. Entity product key productId=GHENGIS_KHAN;. ErrorCode:ItemDoesNotExist (Caused by com.enactor.core.database.NoSuchRecordException: productId=GHENGIS_KHAN; ErrorCode:ItemDoesNotExist Key:productId=GHENGIS_KHAN;)' ]:Execute Action:'WaitForSignOn' State:'StartPeripheralsPleaseWait' Process:'Pos/StartUp'
2023-08-03 14:41:06.888 [AWT-EventQueue-0] DEBUG Process.Pos.StartUp - > Action Inputs for 'WaitForSignOn' are [ enactor.mfc.BaseCurrency='com.enactor.mfc.currency.Currency@6d83865e' ]:Execute Action:'WaitForSignOn' State:'StartPeripheralsPleaseWait' Process:'Pos/StartUp'

Although the exception is handled, it is not informative.

Add an exception link from the State in the LoadProductAtStartup process. The link will go to an action with the class com.enactor.coreUI.actions.UILogMessageAction. Add an input to the log message action with a name of Exception and type of java.lang.Throwable. The Exception data type is a magic input that will be automatically made available from the state even if it is not declared. The mappings of the action will confirm this.

Add a new state, with class com.enactor.coreUI.states.UIErrorState. The UIErrorState automatically displays the details of the last exception caught.

Add an End Process action with the outcome of Fail. The new Fail outcome is already handled by the calling process.

The the process should now look like the screenshot below.

LoadProductAtStartup process snapshot 2

A copy of the process at this state is here (rename before importing to Eclipse): LoadProductAtStartup snapshot 2

If all has gone well, when the POS starts up, it should display a message to the user:

POS Error message

Extensions

Change the handling of the exception so it is caught at the Process level. How does this change the behaviour of the POS? How does this change the logging?

Change the Product ID to a valid ID. Does the exception go away?