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 anoheto.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 aswsnoheto.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 requestObject 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 | |
---|---|---|---|
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 | PROP_OBJECT_STRUCTURE | |
resourceObject | The object in its state after the invocation of the "save" method (class | 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.