Adding Custom Validation
This section describes how to create custom validators in Atoti Sign-Off.
Overview
We expect users to utilize the ISignOffDefinitionService
and IAdjustmentService
to perform transactional CRUD operations. These services come with
built-in validation constraint handling using Java Bean Validation.
However, you can customize the validation performed when creating or updating sign-off definitions and adjustments
by adding beans that implement the ISignOffDefinitionValidator
and IAdjustmentValidator
interfaces, respectively.
info
We expect Sign-Off tasks to be created or updated via the workflow, hence why they are not included in the list of services that can be customized.
How it works
Atoti Sign-Off uses custom constraint annotations to validate the input of the services. For example, when creating a Sign-Off definition, the validation works as follows:
-
The input parameter of
ISignOffDefinitionService::createSignOffDefinition
is annotated with@SignOffDefinitionConstraint(type = CREATION)
.
This tells Spring: when passing anISignOffDefinition
to this method, use the custom@SignOffDefinitionConstraint
annotation with the typeCREATION
to validate theISignOffDefinition
.
This means that if you include your own implementation ofISignOffDefinitionService
, the constraint will automatically be applied. -
@SignOffDefinitionConstraint
is annotated with@Constraint(validatedBy = SignOffDefinitionConstraintValidator.class)
.
This tells Spring: when you see the@SignOffDefinitionConstraint
annotation, use theSignOffDefinitionConstraintValidator
to validate the input. -
The
SignOffDefinitionConstraintValidator
class implements Jakarta’sConstraintValidator
interface, which is used to validate the input. TheisValid
method is called when the input is passed to the method. This method calls theISignOffDefinitionValidator
implementation to actually validate the input.note
You can expose your own implementation of
SignOffDefinitionConstraintValidator
to override theisValid
method, but in order to do so, you must extend the concrete classSignOffDefinitionConstraintValidator
so that your implementation can be picked up by@SignOffDefinitionConstraint
. The Jakarta validation API requires a concrete class to be used as the validator, so we cannot use an interface. -
The
ISignOffDefinitionValidator
implementation is used to validate the input. If you want to add your own custom validation logic, expose an implementation of this interface.
Default Validation
We perform the following validations out-of-the-box:
Sign-Off Definitions
On Creation
The default ISignOffDefinitionValidator
validates the following:
- The
id
field is not populated. - The
scope
field does not overlap with another definition’s scope. See the overlapping scopes section for more details.
On Update
The default ISignOffDefinitionValidator
validates the following:
- The
id
field is populated. - The
name
field has not changed, as it is required as a key for the workflow. - The
startDate
field has not changed, as it is required as a key for the workflow. - The
scope
field does not overlap with another definition’s scope. See the overlapping scopes section for more details.
Sign-Off Adjustments
On Creation
The default ISignOffAdjustmentValidator
validates the following:
- The
id
field is not populated.
On Update
The default ISignOffAdjustmentValidator
validates the following:
- The
id
field is populated.
Custom Validation
To implement your own custom validation you must expose an implementation of ISignOffDefinitionValidator
or IAdjustmentValidator
as a Spring Bean. This will
then replace our default validation logic. You may extend our logic by letting your implementation extend SignOffDefinitionValidator
or AdjustmentValidator
respectively.
Sign-Off Definitions
To provide custom validation for sign-off definitions, create a class that implements the ISignOffDefinitionValidator
interface and override its methods. The
interface is as follows:
/**
* <b>ISignOffDefinitionValidator</b>
*
* <p> The validator used for validating Atoti Sign-Off Definitions.
*
* @author ActiveViam
*/
public interface ISignOffDefinitionValidator {
/**
* @param definitionToCreate the definition to be created
* @return validation errors if the definition is not valid for creation, empty list otherwise
*/
List<IValidationError> validateSignOffDefinitionCreation(ISignOffDefinition definitionToCreate);
/**
* @param definitionToUpdate the definition to be updated
* @return validation errors if the definition is not valid for update, empty list otherwise
*/
List<IValidationError> validateSignOffDefinitionUpdate(ISignOffDefinition definitionToUpdate);
}
A sample implementation of ISignOffDefinitionValidator
could look like this:
public class MyCustomSignOffDefinitionValidator implements ISignOffDefinitionValidator {
List<IValidationError> validateSignOffDefinitionCreation(ISignOffDefinition definitionToCreate) {
if (definitionToCreate.getName() == null){
return List.of(DefaultValidationError.builder()
.source(definitionToCreate)
.reason("The definition name cannot be null")
.action("Please ensure the name field is populated.")
.build());
}
return Collections.emptyList();
}
List<IValidationError> validateSignOffDefinitionUpdate(ISignOffDefinition definitionToUpdate) {
if (definitionToUpdate.getName() == null) {
return List.of(DefaultValidationError.builder()
.source(definitionToUpdate)
.reason("The updated definition name cannot be null")
.action("Please ensure the name field is populated.")
.build());
}
return Collections.emptyList();
}
}
Sign-Off Adjustments
To provide custom validation for sign-off adjustments, create a class that implements the ISignOffAdjustmentValidator
interface and override its methods. The
interface is as follows:
/**
* <b>ISignOffAdjustmentValidator</b>
*
* <p> The validator used for validating Atoti Sign-Off Adjustments.
*
* @author ActiveViam
*/
public interface ISignOffAdjustmentValidator {
/**
* @param adjustmentToCreate the adjustment to be created
* @return validation errors if the adjustment is not valid for creation, empty list otherwise
*/
List<IValidationError> validateSignOffAdjustmentCreation(IAdjustment adjustmentToCreate);
/**
* @param adjustmentToUpdate the adjustment to be updated
* @return validation errors if the adjustment is not valid for update, empty list otherwise
*/
List<IValidationError> validateSignOffAdjustmentUpdate(IAdjustment adjustmentToUpdate);
}
A sample implementation of ISignOffAdjustmentValidator
could look like this:
public class MyCustomSignOffAdjustmentValidator implements ISignOffAdjustmentValidator {
List<IValidationError> validateSignOffAdjustmentCreation(IAdjustment adjustmentToCreate) {
if (adjustmentToCreate.getSignOffTaskId() == null){
return List.of(DefaultValidationError.builder()
.source(adjustmentToCreate)
.reason("The adjustment's task ID cannot be null")
.action("Please ensure the task ID field is populated.")
.build());
}
return Collections.emptyList();
}
List<IValidationError> validateSignOffAdjustmentUpdate(IAdjustment adjustmentToUpdate) {
if (adjustmentToUpdate.getSignOffTaskId() == null) {
return List.of(DefaultValidationError.builder()
.source(adjustmentToUpdate)
.reason("The adjustment's task ID cannot be null")
.action("Please ensure the task ID field is populated.")
.build());
}
return Collections.emptyList();
}
}