Event Validation
In addition to standard entity and DTO validation, SOKit provides a powerful mechanism for handling validation as part of the event system. This is known as event-based validation.
These validation events are added by the resolver during an operation's setup phase.
We saw, for example, that the EventsResolver class might register ValidateRemark.class as a pre-event for the REJECT operation.
Once added, these validation events are executed automatically by the framework.
Below is a validation event used during the REJECT operation:
package com.dev.registration.company.flow.events;
import static com.strategyobject.sokit.extensions.core.utils.ObjectUtils.isNotEmpty;
import static com.strategyobject.sokit.extensions.core.utils.StringUtils.trim;
import com.dev.registration.company.data.CompanyEntity;
import com.dev.registration.company.document.CompanyDocument;
import com.dev.registration.company.document.Operations;
import com.dev.registration.company.document.StartOperations;
import com.dev.registration.company.document.States;
import com.dev.registration.company.document.document.events.CompanyOperationEvent;
import com.dev.registration.company.flow.exceptions.EmptyRemarkException;
import com.dev.registration.company.flow.exceptions.NotEmptyVatNumberException;
import lombok.RequiredArgsConstructor;
import com.strategyobject.sokit.extensions.document.DocumentTransaction;
import com.strategyobject.sokit.extensions.document.api.annotations.DocumentAware;
@DocumentAware(CompanyDocument.class)
@RequiredArgsConstructor
public class ValidateRemark implements CompanyOperationEvent {
@Override
public void execute(
DocumentTransaction<States, StartOperations, Operations, String, CompanyEntity> transaction) {
final var entity = transaction.entity();
if (entity == null) {
return;
}
final var remark = trim(entity.getRemark());
final var vatNumber = trim(entity.getVatNumber());
entity.setRemark(remark);
if (!isNotEmpty(remark)) {
throw EmptyRemarkException.builder().build().path("remark");
}
if (!isEmpty(vatNumber) ) {
throw NotEmptyVatNumberException.builder().build().path("vatNumber");
}
}
}
In particular the REJECT operation is called, this validation ensures that the remark field is not empty.
If the remark is missing, a SOKit exception EmptyRemarkException is thrown with the appropriate path set.
In addition, we also check that the vatNumber is empty in the rejection phase.
You can notice the following:
- The event is associated with a specific document via
@DocumentAware - It implements
CompanyOperationEvent, the interface used to define custom operation logic - Inside the
execute(...)method, we retrieve the entity and run custom validation logic - If the validation fails, we throw a custom SOKit exception, in this case,
EmptyRemarkException.
For the APPROVE we saw that the VAT_NUMBER inserted by the officier should mandatory, so we have to implement it:
package com.dev.registration.company.flow.events;
import static com.strategyobject.sokit.extensions.core.utils.ObjectUtils.isNotEmpty;
import static com.strategyobject.sokit.extensions.core.utils.StringUtils.trim;
import com.dev.registration.company.data.CompanyEntity;
import com.dev.registration.company.document.CompanyDocument;
import com.dev.registration.company.document.Operations;
import com.dev.registration.company.document.StartOperations;
import com.dev.registration.company.document.States;
import com.dev.registration.company.document.document.events.CompanyOperationEvent;
import com.dev.registration.company.flow.exceptions.EmptyVatNumberException;
import lombok.RequiredArgsConstructor;
import com.strategyobject.sokit.extensions.document.DocumentTransaction;
import com.strategyobject.sokit.extensions.document.api.annotations.DocumentAware;
@DocumentAware(CompanyDocument.class)
@RequiredArgsConstructor
public class ValidateVatNumber implements CompanyOperationEvent {
@Override
public void execute(
DocumentTransaction<States, StartOperations, Operations, String, CompanyEntity> transaction) {
final var entity = transaction.entity();
if (entity == null) {
return;
}
final var vatNumber = trim(entity.getVatNumber());
entity.setRemark(vatNumber);
if (!isNotEmpty(vatNumber)) {
throw EmptyVatNumberException.builder().build().path("vatNumber");
}
}
}
In the example below, we have implement the validation of shareholders percentage. In particular, this loop adds up the shareholder percentages one by one. As soon as the total goes over 100%, we stop and throw an error, no need to check all items.
package com.dev.registration.company.flow.events;
import com.dev.registration.company.data.CompanyEntity;
import com.dev.registration.company.data.ShareholderEntity;
import com.dev.registration.company.document.CompanyDocument;
import com.dev.registration.company.document.Operations;
import com.dev.registration.company.document.StartOperations;
import com.dev.registration.company.document.States;
import com.dev.registration.company.document.document.events.CompanyOperationEvent;
import com.dev.registration.company.flow.exceptions.ExceedPercentageException;
import lombok.RequiredArgsConstructor;
import com.strategyobject.sokit.extensions.document.DocumentTransaction;
import com.strategyobject.sokit.extensions.document.api.annotations.DocumentAware;
@DocumentAware(CompanyDocument.class)
@RequiredArgsConstructor
public class ValidateShare implements CompanyOperationEvent {
@Override
public void execute(
DocumentTransaction<States, StartOperations, Operations, String, CompanyEntity> transaction) {
final var entity = transaction.entity();
if (entity == null) {
return;
}
double totalPercentage = 0;
for (var shareholder : transaction.entity().getShareholders()) {
totalPercentage += shareholder.getPercentage();
if (totalPercentage > 100.0) {
throw ExceedPercentageException.builder().build().path("percentage");
}
}
}
}