-
Notifications
You must be signed in to change notification settings - Fork 460
Description
Search before asking
I have read and referred to similar issues in the issues .
- I searched in the issues and found nothing similar.
Motivation
This enhancement aims to integrate the Bean Validation API into the Fesod reading lifecycle, allowing users to define validation rules declaratively using annotations on their POJOs. This will eliminate the need for redundant and complex validation logic—such as long chains of if-else statements—within ReadListener implementations, resulting in a more robust and maintainable data validation solution.
Solution
To provide comprehensive support for Bean Validation, we must address the namespace change from javax.validation to jakarta.validation that occurred after Java 8. Given that Fesod supports a wide range of JDKs (8 through 25), a flexible, multi-module approach is required.
Content
- A
ValidatorSPI (Service Provider Interface) will be defined within the mainfesodmodule. This ensures the core library remains decoupled from any specific validation API. - Create two separate adapter modules:
fesod-bvalwill adapt Fesod's SPI to thejakarta.validationAPI.fesod-bval-javaxwill be created by duplicatingfesod-bvaland modifying the imports to support the legacyjavax.validationAPI.
- The adapter modules will only depend on the standard Bean Validation APIs. The user is free to choose and provide any compliant implementation (e.g., Hibernate Validator, Apache BVal).
graph TD
app["Application"]
subgraph "SPI Definition"
A["fesod"]
end
subgraph " "
B["fesod-bval (for jakarta)"]
C["fesod-bval-javax (for javax)"]
end
subgraph " "
D["hibernate-validator"]
E["apache bval-jsr"]
F["others"]
end
A --> B
A --> C
app -.-> A
app -.-> adapters{"Choose One SPI Implementation"}
adapters -.-> B
adapters -.-> C
app -.-> beanvalidation{"Choose One Bean Validation Implementation"}
beanvalidation -.-> D
beanvalidation -.-> E
beanvalidation -.-> F
API Usage Example
1. Add Dependencies
<dependency>
<groupId>org.apache.fesod</groupId>
<artifactId>fesod</artifactId>
<version>${fesod.version}</version>
</dependency>
<dependency>
<groupId>org.apache.fesod</groupId>
<artifactId>fesod-bval</artifactId>
<version>${fesod.version}</version>
</dependency>
<!-- Users provide their chosen Bean Validation implementation, e.g., Hibernate Validator, Apache BVal -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>${hibernate-validator.version}</version>
</dependency>
<!-- EL Support -->
<dependency>
<groupId>org.glassfish.expressly</groupId>
<artifactId>expressly</artifactId>
<version>${expressly.version}</version>
</dependency>2. Define the POJO with Validation Annotations
public class ExcelModel {
@NotBlank(message = "In sheet '{sheetName}', row {rownum}, column {column}: Name is required")
@ExcelProperty("Name")
private String name;
@Min(value = 20, message = "Age must be greater than or equal to {value}")
@ExcelProperty("Age")
private Integer age;
}3. Read the Excel File (Async)
File file = new File("file.xlsx");
Fesod.read(file, ExcelModel.class, new CustomAnalysisEventListener())
.useValidation(true)
.sheet()
.doRead();The ValidatingAnalysisEventListener provides precise error information through its callback methods. After the entire analysis is complete, a comprehensive Errors object containing all validation failures can be retrieved via a new convenience method on the AnalysisContext. The Errors object contains a detailed list of RowError (for row-level validation) and PropertyError (for cell-level validation) objects.
public class CustomAnalysisEventListener extends ValidatingAnalysisEventListener<ExcelModel> {
@Override
public void onValidated(ExcelModel data, AnalysisContext context) {
// Called for each row that successfully passes validation.
}
@Override
public void onValidated(ExcelModel data, List<RowError> validationErrors, AnalysisContext context) {
// Called for each row that fails validation.
// Each error in validationErrors contains detailed context like sheetNo, sheetName, rownum...
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// Retrieve the full report of all errors
Errors validatedErrors = context.getValidationErrors();
if (validatedErrors.hasErrors) {
// ...
}
}
}4. Support Fesod-Specific Parameters Expression (such as {sheetNo}, {sheetName}, {rownum}, and {column} ...)
Before > "In sheet '{sheetName}', row {rownum}, column {column}: Name is required"
After > "In sheet 'Sheet1', row 1, column 1: Name is required"Alternatives
Regarding compatibility support for the javax.validation namespace, an alternative approach was considered: maintaining only a single source module, fesod-bval (for Jakarta), and then leveraging a build plugin like maven-shade-plugin, transformer-maven-plugin to dynamically transform the jakarta.* namespace into javax.* during the packaging phase.
The intended outcome of this strategy was to generate two distinct artifacts using a classifier, allowing users to choose the one that fits their environment:
<!-- For Jakarta support -->
<dependency>
<groupId>org.apache.fesod</groupId>
<artifactId>fesod-bval</artifactId>
<version>${fesod.version}</version>
</dependency>
<!-- For Javax support -->
<dependency>
<groupId>org.apache.fesod</groupId>
<artifactId>fesod-bval</artifactId>
<version>${fesod.version}</version>
<classifier>javax</classifier>
</dependency>Problem:
The build plugins are unable to modify the dependency declarations within the generated pom.xml file. This would require manual intervention after the build, which is incompatible with and unfriendly to our automated CI/CD processes, such as the GitHub workflows.
Anything else?
No response
Are you willing to submit a PR?
- I'm willing to submit a PR!