API Business Services - Customize error messages

2022.2

In the creation or modification services, when the instance is saved, triggers can raise functional exceptions, in the form of noheto.BusinessException. This type of exception is encapsulated in an API exception to be converted to an error in the response to the service invocation, in a general form described here.

In debug mode, on developpement environment, the BusinessException message is visible as well as the complete stack trace. But in normal mode, in production environment, the messages are purged of detailed or technical information that could be used to penetrate the system. As it is not possible to know in general whether the BusinessException message contains sensitive information, it is only accessible in debug mode.

Sometimes it may be necessary to provide more specific information to an API consumer for various reasons, without providing technical or detailed information, and that is what this extension component allows. For that, you need to implement com.noheto.restapi.APIBusinessExceptionInterceptorAdapter.

methods

  • public boolean accept(String type, String actionName, String objectName, IObjectStructureReadOnly structure)
    This method allows a quick selection of a component according to the service that triggers the exception. If it returns true, the intercept method of this component will be called. This method is called once to determine the components to be used to handle the exceptions raised by the service, and will not be called again for this service.

  • public void intercept(BusinessExceptionContext context)
    This method is called to determine whether to decorate the error with information for the service invocator that informs about the reasons for the BusinessException.

context

It provides the description of the context of the exception and allows to decorate the error with information about the BusinessException

  • void setSubMessage(String subMessage)
    Allows you to indicate the decoration message. Use a vague message, with no sensitive or detailed, let alone technical, information. Hackers may purposely send requests that cause errors in order to collect information that can give them clues about the system and possibly allow them to attempt other requests for malicious actions. Keep in mind that the caller is supposed to know the parameters they are sending in the request.

  • void setSubCode(String subCode)
    Allows you to specify an error code that allows automated processing on the invoker's side.

  • void setLogLevel(org.apache.log4j.Level level)
    By default the exceptions generated to produce the API error message are logged at the DEBUG level. It is possible to change the log level via this method.

  • Throwable getThrowable()
    Returns the exception to be caught. Usually this is a noheto.BusinessException, but it can be other exceptions that may be raised by the save instruction. The API already handles a number of generic errors, but may ask the extension component to handle some errors that are not detected by the API, such as wsnoheto.error.ILocalizedException for example.

  • CTSurfer getSurfer()
    Returns the surfer who invoked the service.

  • Locale getLocale()
    Returns the request locale (not the surfer locale).

  • HttpServletRequest getRequest()
    Returns the request

  • Object getProperty(String property)
    Gets a context property of the exception triggering. Depending on the service, the properties can be different. Here is the list:

Name

Description

Constant in the class BusinessExceptionContext

Name

Description

Constant in the class BusinessExceptionContext

type

the type of service

PROP_TYPE

 

LEGACY_CREATE_UPDATE

legacy create or update services

TYPE_LEGACY_CREATE_UPDATE

 

DAM

dam create or update services

TYPE_DAM

 

DATA

data create or update services

TYPE_DATA

 

PROFIL

profil update services

TYPE_PROFIL

 

MASSIMPORT

massimport create or update services

TYPE_MASSIMPORT

actionName

The name of action

PROP_ACTION_NAME

objectName

The name of object

PROP_OBJECT_NAME

objectStructure

The structure object (class wsnoheto.engine.IObjectStructureReadOnly)

PROP_OBJECT_STRUCTURE

resourceObject

The object in its state after the invocation of the "save" method (class wsnoheto.engine.IObjectWritable). (not available for legacy services)

PROP_OBJECT

serviceId

ID of service (not available for legacy services)

PROP_SERVICE_ID

serviceType

Type of service (not available for legacy services)

PROP_SERVICE_TYPE

endpointType

Type of endpoint (not available for legacy services)

PROP_ENDPOINT_TYPE

contextName

Context name (not available for legacy services)

PROP_CONTEXT_NAME

requestMode

Request mode (not available for legacy services)

PROP_REQUEST_MODE

finalMode

Final mode (not available for legacy services)

PROP_FINAL_MODE

resourceName

Name of resource (not available for legacy services)

PROP_RESOURCE_NAME

  • Set<String> getProperties()
    Gives the list of all existing properties for this interception.

  • String getType()
    Returns the type of invocation. Corresponds to a call ofgetProperty(BusinessExceptionContext.PROP_TYPE).

  • String getActionName()
    Returns the name of the action. Corresponds to a call ofgetProperty(BusinessExceptionContext.PROP_ACTION_NAME).

  • String getObjectName()
    Returns the name of the object. Corresponds to a call ofgetProperty(BusinessExceptionContext.PROP_OBJECT_NAME).

  • IObjectStructureReadOnly getObjectStructure()
    Returns the object that represents the structure of the object. Corresponds to a call ofgetProperty(BusinessExceptionContext.PROP_OBJECT_STRUCTURE).

  • void setData(String key, Object data)
    Allows to store data in the context. When there are several components that correspond to the service, we call them one after the other until we find one that specifies a submessage. It is possible to communicate between these calls via the data stored in the context (to avoid for example decoding the same information several times)

  • <T> T getData(String key)
    Returns a store data

Example

For this example, we use a simple object, named testbusinessexception:

 

And we configure a legacy creation and modification action named A_BUSINESSEXCEPTIONTEST:

{ "objectname": "testbusinessexception", "description": "Test interception Business Exception", "update": true, "create": true, "patch": true, "workflow": false, "props": { "name": { "i18n": false } }, "reportprops": [ "name" ] }

The URI we will invoke allows us to create a new instance with a name that will trigger the exception when it is equal to "exception".

POST https://example.host.com/api/json/create/A_BUSINESSEXCEPTIONTEST?prop_name=exception

In debug mode, this URI will generate a response like this (The stacktrace has been deliberately removed to reduce the example), in debug mode, on a developpement environnement:

{ "message": "A functional error has occurred: Error test, create testbusinessexception/0. ", "error": "400/7", "errtag": "f7", "errorSupportDetails": [ { "value": "Wedia support email: support@wedia-group.com.", "key": "WEDIA_ERROR_SUPPORT", "params": [ "support@wedia-group.com" ] } ], "error_uri": "http://localhost:9080/enginepkg/api/rest/errors#400/7", "errorWithContact": false, "cause": { "exception": "com.noheto.apirest.core.exceptions.BusinessException", "message": "Business exception: Error test, create testbusinessexception/0", "stacktrace": [ ], "cause": { "exception": "noheto.BusinessException", "message": "Test erreur, create testbusinessexception/0", "stacktrace": [], "cause": null } }, "version": "2.3", "status": 400, "time": 246 }

In normal mode (debug=false), on developpement environment, the response will be:

{ "message": "A functional error has occurred: Error test, create testbusinessexception/0. ", "error": "400/7", "errtag": "f6", "errorSupportDetails": [ { "value": "Wedia support email: support@wedia-group.com.", "key": "WEDIA_ERROR_SUPPORT", "params": [ "support@wedia-group.com" ] } ], "error_uri": "http://localhost:9080/enginepkg/api/rest/errors#400/7", "errorWithContact": false, "version": "2.3" }

In the both, we can see the business exception original message. In the second version, the stacktrace is not present.

On a production environment the response will be:

The business exception original message is not visible.

Setting the business service

First, here is the trigger code that will generate the exception:

Next, here is the implementation of the interceptor:

Here is the localization resource bundle:

In debug mode, on developpement environment, we get this response:

In normal mode (debug=false), on development environment, we get:

Finally, on production environment, the response will be:

Note the error code is not the same. The original encapsulation exception, code 400/7, has not been changed, for backward compatibility and documentation reasons. A new exception has been created with code 400/19.