Handler types #
As mentioned in Development Cycle, Livebase Handlers are divided into two groups: model-dependent ones, which depend on the classes and other elements defined in a model, and the default ones, which exist regardless of the model used.
Modelling Handlers are in turn divided into two families: Persistency Handlers and Interaction Handlers:
- Persistency Handlers: includes the SaveActionHandler, the ValidationHandler and the three DatabaseHandlers (DatabaseInsert, DatabaseUpdate, DatabaseDelete). They are used to manage the different steps in the process of saving/modifying an object and are called in the order we have listed them. The SaveActionHandler allows you to manage the fields of a write form, the ValidationHandler to perform a custom validation of a class instance, and finally the DatabaseHandler are in the commit phase of the database transaction.
- Interaction Handlers: includes only theFormActionHandlers, that are triggered by custom events defined in one or more Application Schema; these events are usually invoked through appropriate GraphQL queries.
The default handlers are the AuthenticationHandler, the LogoutHandler and the ScheduledTask).
Modelling Handlers #
To add a handler to a model class, from the Database Schema , open the Class menu (by right-clicking on the class header) and select the corresponding
New handler from the available ones, as shown in the picture:
Once the type of Handler has been chosen we need to:
- Type a name, which must be unique in the namespace of the class.
A good practice is to use a name representative of the action we want to perform, e.g. sendEmail if the Handler will send an email.
- For some Handlers (including the FormActionHandler) additional configuration is required in the Application Schema .
- For some Handlers extra options are also available in the Service Handler context menu, accessible by right-clicking on the Handler from the Database Schema .
Handlers and namespace #
Handlers defined on a class share a separate namespace from the attribute namespace. In the diagram they are listed in another list below the attribute list; for example, shown in figure there are three different Handlers on Class1.
Handlers and application views #
Similarly to the other elements of the diagram - classes, attributes, relations - it is possible to enable/disable a Handler for a specific application view: to do so, just click on the Handler from the Application Schema to toggle its state, as illustrated in the tutorial (Define application views and user profiles). This allows you to choose in which application views to allow the use of a certain plugin on that class.
Persistency Handlers #
It is invoked whenever the GraphQL save service is used.
The action will be executed before the data are validated and saved on the database; this allows to manage in writing the fields filled in by the user and eventually modify their content (for example, formatting the native attributes or adding information on the database and linking them to the created object by initializing default relationships).
The developer can specify the operations to be performed by the handler by implementing the
doAction() method of the related class, and interacting with the context (SaveActionHandlerContext) which is its parameter.
The entity can be accessed either in the pre-modified or current version, and in the latter case both the internal fields of the entity and those of any related objects can be written.
The handler can only return an empty result by calling
none() on the result object offered by the context.
It is called after the SaveActionHandler (if present on the same class) and before committing a transaction to the Database related to one of the operations (
update) on the instances of a specific class. This Handler allows you to implement custom validation logic for the class instance, with which you can control rules that cannot be expressed in the model (e.g., through restriction of the attributes domain, Class warnings, or through rules whose specificity makes the low-code approach too onerous, like making a call to an external service to validate fields.
The developer defines the validation process by implementing the
validate() method of the handler class, and interacting with the context (ValidationHandlerContext) that is its parameter.
The entity involved may be accessed in the previous and current versions, but only in read mode.
The handler must return a result indicating the success or failure of the custom validation. To do this, once you have the result, you must make one of the following calls:
success(): To report the success;
fail(msg): To report a negative result; this call must be provided with a string representing an error message, which will be displayed in a dialog box while the application runs.
Access to the set of Cloudlet locale values, as defined within the Designer, is also offered by invoking the
Blocking ValidationHandler #
By default, the additional validation performed by a ValidationHandler is non-blocking: if the save action is not successfully validated, the application displays a warning allowing the user to confirm or deny it, similar to the non-blocking Class warning.
We can change this behaviour by adjusting the
Blocking flag in the context menu accessible by right-clicking on the ValidationHandler: checking this option will cause validation to be blocked, and if validation is negative, the framework will reject the save action.
failure(msg) method has an optional boolean parameter, which allows you to override the behavior specified in the Designer in certain cases: a value of
true will make the validation blocking, and a value of
false will make it non-blocking.
For each transaction there is a specific variant of this Handler: DatabaseInsertHandler, DatabaseUpdateHandler and DatabaseDeleteHandler. Each is called before and after committing a transaction to the Database related to one of the operations (
delete) on instances of a specific class. In the case of the first two, this always happens after the ValidationHandler (if it is present on the same class). These handlers allow you to perform actions that depend on the content of a transaction, e.g. run a custom SQL query.
You can change the type of transaction calling the DatabaseHandler from the
Change type item in the Handler’s context menu.
Each DatabaseHandler requires two methods to be implemented by the developer:
beforeCommit(), to specify what to do before committing the transaction, and
afterCommit(), for operations after the same commit. In the first case, the context offered is a TransactionalContext, and in the second case, a Context.
Execution contexts #
Entity read-only access is allowed, and depending on whether you are working before or after the commit, you may have visibility of the current (new) and/or previous (old) version:
|Handler type||Implemented method||Access|
The only result that can be returned by a DatabaseHandler’s methods is a positive conclusion, which can be obtained by invoking the
none() method on the result.
Blocking DatabaseHandlers #
By default, DatabaseHandlers do not cause exceptions when they are not implemented. From the Service handler menu you can check the
Implementation required option and force the application to request the execution of a Plugin that implements the corresponding Handler, blocking persistence on the DB if the Plugin is not present in the Cloudlet or if it is inactive.
Interaction handlers #
It is invoked through GraphQL.
Unlike Persistency Handlers, which are triggered in a specific sequence, FormActionHandlers can perform their own sequence of actions at any time. These actions must be specified by implementing the
doAction() method of the related class and interacting with the FormActionContext which is its parameter.
The Entity referenced by the form is accessible in the version being edited, both read and write. You can also access the session with the database, in its mutable version, with the
getEntitySession() method offered by the context.
Additional features offered by the context are:
- The ability to get information about the event that triggered the handler, via the
getEventSource()method. This information includes the type of event (click on button or onChange) and the form field it is attached to;
- Accessing the Cloudlet’s file storage via the
- Accessing a JSON of parameters passed by a client via the
The results that can be returned by a FormActionHandler are:
none(): a positive result that takes no further action;
withMessage(msg): displays a dialog containing the message passed as a parameter; this call must be followed by one specifying the severity level of the message:
info(): simple information message, no error value;
warning(): warning or error message, generally not blocking;
error(): blocking error message.
The following result types are still in the experimental stage, thus not fully supported by the GraphQL schema:
download(file, fileName): orders the download of a file. Optionally you can also pass the name to save that file on the target filesystem;
preview(file, fileName): opens a new tab in the Cloudlet runtime browser, containing a preview of a file; from this tab you can decide whether to download the file, and again there is an optional parameter for specifying the name to save it with;
withPayload(json): returns a JSON, specified as a string and passed as a parameter, to a client accessing the Cloudlet.
FormActionHandler and Form class #
The FormActionHandler service handler menu offers the
Set form... command, which allows you to link a Form class to the class on which the Handler is defined.
This way the Handler inside Class1 will also have the context of myForm (all fields filled in with their values) available to it: this is achieved by calling
getHandlerParams() on the FormActionHandler context.
A Form Class, like normal classes, can have FormActionHandlers attached to its buttons or onChange() events, in which case you can access the filled fields via the getEntity() method and the possible results are the same as a handler defined on other non-Form classes.
Default Handlers #
It is called at the time of authentication of a Cloudlet member, i.e. when the user enters username and password and clicks the
Login button. This information is passed to the AuthenticationHandler, which can perform an authentication with custom rules 'e.g. run additional checks on the Database or coordinate with an external service.
The developer must implement the
authenticate() method of the
SpiCloudletAuthenticationHandler interface, specifying the actions to perform at login time. The same method offers a parameter of type AuthenticationData, which contains information about the type of authentication that was performed.
It is called after a click on the
Logout button to perform a custom action at this point in the execution ’ for example, redirecting the browser to another page (especially useful if the generated application is linked to a custom front-end).
The developer must implement the
logout() method of the
SpiCloudletLogoutHandler interface, with the sequence of actions to execute at logout time. The context that is its parameter is a LogoutHandlerContext.
It offers the following utility methods:
getAuthenticationData(): Gets the AuthenticationData structure mentioned in the previous section, which contains authentication information;
get__CloudletCurrentUser(): Gets the information about the Cloudlet user who logged out.
The LogoutHandler must return a result specifying whether or not a redirect to another URL is needed. In the first case, you must call the
none() method on the structure obtained by calling
result(); in the second case, you must call the
sendRedirect(url) method with the specification of the URL to redirect to.
The ScheduledTasks can be used to schedule routines independently of events occurring on the application; for each task we can configure the frequency using a CRON expression (whose summary can be found here).
The developer must implement the
run() method of the SpiCloudletScheduledTask interface, and decorate the defined class with the
@CloudletScheduledTask annotation. In the annotation, specify the CRON expression that defines the run frequency. The
run() method is equivalent to a procedure, therefore it must not return any results.