Tips & Tricks - Groovy web error handler

Foreword

Custom error handlers can be defined in Intrexx. These allow you to provide more specific and expressive error messages for users when errors occur in processes or Velocity scripts.

Error handler file

The user-defined error handlers are available in the portal directory /internal/system/vm/html/errorhandler/custom/. A error handler file must meet the following conventions: The filename has to

  • begin with "handler_"

  • followed by a two digit number and an underscore

  • and have the file extension .groovy

The folder already contains an example file called "handler_50_xxx.groovy.example". The title between the digits (including underscore) and the file extension is free choice. Error handlers are sorted lexicographically and processed in this order. Processing ends with the first handler that defines a title and optionally a description. Therefore, you should make sure that a check is made as to whether the error should be handled separately before defining the title for the error message in the browser. The example file "handler_50_xxx.groovy.example" demonstrates an implementation example:

/*

	The variable g_exception contains the exception that indicates the error.

	Additional methods added to java.lang.Throwable:

	g_exception.getCauseOfType(SessionException)
	g_exception.getCauseOfType("de.uplanet.lucy.server.session.SessionException")

	g_exception.getCauseOfType(WorkflowException)
	g_exception.getCauseOfType("de.uplanet.lucy.server.workflow.WorkflowException")

	g_exception.isCausedBy(WorkflowException)
	g_exception.isCausedBy("de.uplanet.lucy.server.workflow.WorkflowException")

	g_exception.getRootCause()
	g_exception.isCausedByAccessControlException()
*/


// decide if we must handle the error or not
def bMyError = g_exception.isCausedBy(java.lang.Exception)

if (bMyError)
{
	switch (g_language)
	{
		case "de":
			title        = "Bitte Titel angeben"
			description  = "Bitte Beschreibung angeben. ${g_exception.message}"
			showEmbedded = false // equivalent to showBare = true
			break

		default:
			title       = "Please provide a title"
			description = "Please provide a description: ${g_exception.message}"
			showEmbedded = false // equivalent to showBare = true
			break
	}
}

With

def bMyError = g_exception.isCausedBy(java.lang.Exception)

a test is performed as to whether the triggering exception is an exception that should be responded to in a user-defined manner. If the triggering exception has the type "java.lang.Exception", the returned value "bMyError" is true. The subsequent "if" condition is therefore met. The defined error message us shown in the browser depending on the current language. The following properties can be set to define the error message:

Property

Possible values: see description

title

Text for the title bar in the browser error message

description

Description text in the browser error message

showEmbedded

By entering "true" or "false" you can control whether the another notification window will be shown in front of the actual error message. If "true" is entered, the window looks like this:

The user-defined error box is shown when the user clicks on "More information".

The defined "title" and "description" properties and shown in the browser if an error occurs: The processing order defined by the lexicographical order would therefore be terminated at this point after displaying the error message.

Error handler in use

Exception analysis

In order to make it easier to find an exception within a stack trace, or to simplify the search for the triggering exception, the g_exception object includes methods that analyze the error object and return relevant results.

g_exception.getCauseOfType(Class)
g_exception.getCauseOfType(String)

In this error object, a search for the first occurrence of the current exception is performed. If the search is successful, then the cause will be returned, otherwise null. The error type to be searched for can be transferred as a class object or a string with a fully qualified class name. Example:

g_exception.getCauseOfType("java.io.FileNotFoundException")
g_exception.getRootCause()

Returns the originally underlying exception.

g_exception.isCausedBy(Class)
g_exception.isCausedBy(String)

Checks whether the current exception has the same type as the transferred exception. This returns true if the types match, otherwise false. Example:

def bIsError = g_exception.isCausedBy("java.io.FileNotFoundException")
g_exception.isCausedByAccessControlException()

Checks whether an exception has occurred due to missing access permissions (e.g. data group rights etc.). Returns true if this is the case, otherwise false. Example:

def bNoPermission = g_exception.isCausedByAccessControlException()

Please note that, for all of the methods listed here, the object hierarchies and the inheritance will be taken into account. Thus, the test for a java.io.Exception also includes the test for a java.io.FileNotFoundException, for example.

Contextual handling of exceptions

In the previous examples, exceptions were only checked in general. However, since the same type of exception may occur in different processes or applications, and each of these should be seen individually based on the context, there needs to be a way to make this contextual reference. There are various ways to implement such distinctions. One option is to always provide exceptions with a contextual error message. This creates an option to filter the error message again within the error handler, and to only start the custom processing if the filter applies. To provide you with inspiration, we have provided examples of special error codes, application-related abbreviations or similar. Example:

throw new FileNotFoundException("PROJ_VERW_001")

Marks the error message with a contextual abbreviation allowing it to be preselected accordingly:

if (g_exception.isCausedBy(java.io.FileNotFoundException))
{
    def rootCause = g_exception.getRootCause()

    if (rootCause.message != null && 
        rootCause.message.contains("PROJ_VERW_001"))
        bMyError = true
    else
        bMyError = false
}

if (bMyError)
{
    switch (g_language)
    {
//...
        case "de":
            title        = "Projektverwaltung Fehler 001"
            description  = "Pflichtenheft-Datei wurde nicht gefunden."
            showEmbedded = false
            break
//...
    }
}

With both "if" queries

if (g_exception.isCausedBy(java.io.FileNotFoundException))

and

if (rootCause.message != null && 
    rootCause.message.contains("PROJ_VERW_001"))

the custom error handler will then only be addressed and used when there is a FileNotFoundException, and, in addition, the code PROJ_VERW_001 is found. Other exceptions of this type, but without the code, will not be handled separately. As an alternative to the assignment of application-related abbreviations, session and/or request values, or also shared state variables, can be used to realize contextual processing. In the following example an exception will only be handled separately if the user is in a specific application. This selection will be achieved by querying the request variable "rq_AppGuid".

def bMyError = false

if (g_exception.isCausedBy(java.io.IOException) && 
    g_request["rq_AppGuid"] == "93BDFE88ADC4BAAB66630548C61B8ADD6F287AA6")
    bMyError = true

if (bMyError)
{
    switch (g_language)
    {
...
        case "de":
            title        = "Projektverwaltung"
            description  = """Es ist ein Fehler in der Applikation 
Projektverwaltung aufgetreten."""
            showEmbedded = false
            break
...
    }
}