This is an internal documentation. There is a good chance you’re looking for something else. See Disclaimer.
Validators¶
Validators are executed before the commit and are used to ensure data quality. They are executed in java and may throw error messages if something is not quite right.
Warning
Custom java validators must not be used if the requirement can be satisfied using standard entity validators (See Validation)
If a validator is required it can be created by implementing extending the AbstractEntitiesValidator
class and implementing the validate
method. The java code evaluates all entities of one entity model that were changed in a given transaction (first parameter of the validate
method). If the entity validates successfully, nothing should be done
in the validator. If it does not validate the validationResults.get(entity).setError
can be used to cancel the transaction and show an error message for each faulty entity. The message
can contain variables that may be filled in as described in Text-Resources.
Please find below an example that validates entity
and throws an error if relTest_status
is not null and mandatory_if_status_set
is not filled in.
@Validator(filter = "Test_entity")
public class TestValidator extends AbstractEntitiesValidator {
@Override
public void validate(List<Entity> entities, Map<Entity, EntityValidationResult> validationResults) {
entities.stream()
.filter(entity -> entity.getRelatedEntityOrNull("relTest_status") != null
&& Strings.isNullOrEmpty(entity.getString("mandatory_if_status_set"))
.forEach(entity -> validationResults.get(entity).setError(new TextMessage("validation.TestValidator.error_message"));
}
}
Tip
Entity validation runs in the NULL business unit.
EntitiesValidator vs. EntityValidator¶
To optimise entity validation while importing entities the interface was changed for single entity validation (AbstractEntityValidator
) to the current multi
entity validation. If entities / data structures need to resolved this can and should be done once per transaction instead of once per entity. See for example the
validator below. It checks if a default account, payment condition and currency is configured. This can now be done once per business unit instead of once
per entity which reduces the number of queries while importing addresses significantly.
@Validator(filter = "Debitor_information")
public class DebitorInformationValidator extends AbstractEntitiesValidator {
private static final String ERROR_MESSAGE = "validation.DebitorInformationValidator.missingDefaultMsg";
private final Context context;
private final QueryBuilderFactory queryBuilderFactory;
private final BusinessUnitManager businessUnitManager;
public DebitorInformationValidator(Context context,
QueryBuilderFactory queryBuilderFactory,
BusinessUnitManager businessUnitManager) {
this.context = context;
this.queryBuilderFactory = queryBuilderFactory;
this.businessUnitManager = businessUnitManager;
}
@Override
public void validate(List<Entity> debitorInformationEntities, Map<Entity, EntityValidationResult> validationResults) {
Multimap<String, Entity> groupedDebitorInfoEntities = groupByBusinessUnit(debitorInformationEntities);
for (String buId : groupedDebitorInfoEntities.keySet()) {
if (defaultAccountNotExists(buId) || defaultPaymentConditionNotExists(buId) || defaultCurrencyNotExists(buId)) {
groupedDebitorInfoEntities.get(buId).forEach(entity -> validationResults.get(entity).setError(new TextMessage(ERROR_MESSAGE)));
}
}
}
private Multimap<String, Entity> groupByBusinessUnit(List<Entity> debitorInformationEntities) {
Multimap<String, Entity> result = ArrayListMultimap.create();
debitorInformationEntities.forEach(entity -> result.put(businessUnitManager.getBusinessUnit(entity).getId(), entity));
return result;
}
private boolean defaultAccountNotExists(String buId) {
return defaultNotExists("Account", "default_summary", buId);
}
private boolean defaultPaymentConditionNotExists(String buId) {
return defaultNotExists("Payment_condition", "default_payment_condition", buId);
}
private boolean defaultCurrencyNotExists(String buId) {
return defaultNotExists("Currency", "default_currency", buId);
}
private boolean defaultNotExists(String entityName, String defaultBooleanFieldName, String buId) {
QueryBuilder queryBuilder = queryBuilderFactory.find(entityName)
.where(field(defaultBooleanFieldName).is(true));
if (hasBuRelation(entityName)) {
queryBuilder.where(field("relBusiness_unit.unique_id").is(buId));
}
EntityList defaultEntities = queryBuilder.build(context).execute();
return defaultEntities.isEmpty();
}
private boolean hasBuRelation(String entityName) {
return ((EntityModel) context.getEntityManager(entityName).getModel()).getBusinessUnitType().isLinked();
}
}
All old validators will still work with the old interface. To make this posisble, a default implementation of the new interface method was added to the old interface. Please do not try to get rid of the old interface by copy-pasting the default implementation to each validator.
default void validate(List<Entity> entities, Map<Entity, EntityValidationResult> validationResults) {
entities.forEach(entity -> validate(entity, validationResults.get(entity)));
}
Registration¶
Validators are usually automatically registered when annotated with the @Validator
annotation. To define which
entities should be validated the filter
parameter can be used as seen in the following examples. If a validator
should be applied to all entities filter="*"
can be used.
Single entity model¶
@Validator(filter = "User")
public class TestValidator extends AbstractEntitiesValidator {
// content
}
Mutiple entity models¶
@Validator(filter = {"User", "Address"})
public class TestValidator extends AbstractEntitiesValidator {
// content
}
Post flush¶
Normal validator are executed before the change is flushed to the database. So queries cannot be executed on the database as the change is not persisted.
With postFlush = true
the validtor is executed after the change is flushed but before the transaction is commited.
For example the QueryBuilderFactory
can be used to verify something.
@Validator(filter = "User", postFlush = true)
public class TestValidator extends AbstractEntitiesValidator {
private final QueryBuilderFactory queryBuilderFactory;
// content
}
Register validator from different module¶
If you need to register a validator from a different module (e.g. a standard validator) this can be done in your
module specific configuration. To reference the validator instance that in most cases is not visible in your module
the class name can be used as parameter name. E.g. public class TestValidator extends AbstractEntitiesValidator
->
EntitiesValidator testValidator
.
@Bean
public EntitiesValidatorContribution testValidatorContribution(EntitiesValidator testValidator) {
EntitiesValidatorContribution contribution = new EntitiesValidatorContribution();
contribution.setValidator(testValidator);
contribution.setFilter("Other_entity");
return contribution;
}