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::createSignOffDefinitionis annotated with@SignOffDefinitionConstraint(type = CREATION).
This tells Spring: when passing anISignOffDefinitionto this method, use the custom@SignOffDefinitionConstraintannotation with the typeCREATIONto validate theISignOffDefinition.
This means that if you include your own implementation ofISignOffDefinitionService, the constraint will automatically be applied. -
@SignOffDefinitionConstraintis annotated with@Constraint(validatedBy = SignOffDefinitionConstraintValidator.class).
This tells Spring: when you see the@SignOffDefinitionConstraintannotation, use theSignOffDefinitionConstraintValidatorto validate the input. -
The
SignOffDefinitionConstraintValidatorclass implements Jakarta’sConstraintValidatorinterface, which is used to validate the input. TheisValidmethod is called when the input is passed to the method. This method calls theISignOffDefinitionValidatorimplementation to actually validate the input.note
You can expose your own implementation of
SignOffDefinitionConstraintValidatorto override theisValidmethod, but in order to do so, you must extend the concrete classSignOffDefinitionConstraintValidatorso 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
ISignOffDefinitionValidatorimplementation 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
idfield is not populated. - The
scopefield 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
idfield is populated. - The
namefield has not changed, as it is required as a key for the workflow. - The
startDatefield has not changed, as it is required as a key for the workflow. - The
scopefield 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
idfield is not populated.
On Update
The default ISignOffAdjustmentValidator validates the following:
- The
idfield 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();
}
}