JSR-380 Bean Validation 2.0 in AEM
I have spent years writing and reviewing code. I have seen a lot of wheel re-invetions. I have even done a few my self 🤘🏽
But as much fun as re-inventing the wheel is, it gets tiresome. And I look for off-the-shelf solutions. For example I wrote an post on Open Feign. And how to avoid writing mountains of HTTP client code.
This post is about validating user input. Something for which I have seen countless "util" classes written. This is insane, given that there is an entire implementation out there for this purpose. That is the Hibernate Validator. The reference implementation of JSR-380.
First, let's start by defining all the dependencies we will need. Open the core/pom.xml file and put this block of dependencies in it.
The first 4 dependencies are
Jakarta Bean Validation 3.0 - This is the JSR-380 API
Hibernate Validator - This is the JSR-380 Implementation
The next 2 are another API/Implementation pair from Jakarta EE. They are for the Hibernate Validator.
And the last two are for convenience. The Jackson JSR-310 data type module because Jackson will need to work with a LocalDate. And Lombok because I don't like to write boilerplate code.
Writing A Unit Test (TDD)
Here is my happy path unit test. I set the JSON in the AEM context request. And call the doPost() method on the servlet. Passing the request & response from the context.
Writing The Servlet and Validation
Now we are going to write the servlet. It will deserialize the JSON into a Java bean. And do the validation. The bean fields get annotated with the appropriate constraint annotation. And if there are any violations, they get streamed back to the response as a JSON object.
Embedding The Dependencies
By far, the hardest part of this exercise was embedding the required dependencies. I am using an AEM Archetype Project v43. And AEM 6.5.18. This should work on AEMaaCS as well. We need to update the core/pom.xml file. Locate the bnd-maven-plugin and update its configuration.
The first 6 includeresource instructions is pretty straight forward. They will take the dependency jars and put them into our own bundle. The last includeresource is a little more interesting. Instead of copying the jar to our bundle, it will unroll it first. Unzipping and putting the contents within our own bundle.
The reason the last package needed to get unrolled was because it is a multi-release jar file. That means it contains a directory /META-INF/versions/9.
This caused the bnd-maven-plugin to issue the following error
Since that directory only contained one file, module-info.class, I excluded it. Because it is not needed. Hence the !/!*/module-info.class filter.
Once the bundle gets built, this is what the contents look like. You can see there all the jar files at the root. And the com.fasterxml package got unrolled.
And here is the bundle manifest. You see the Bundle-Classpath contains the jar files. And the com.fasterxml is a Private-Package. In fact the jar files are also private to my bundle. Nothing got exposed to the OSGi container via the Export-Package header. Except only my own.
Here I created a simple bean called RegistrationInformation. It uses the following constraints
@NotEmpty : Element must not be null nor empty.
@Email : Element has to be a well-formed email address.
@AssertTrue : Element must be true.
If any of these do not suit your needs, you can always create a custom one. To do so is simple. Create the annotation class
And simple annotate the bean field. Here is a short unit test.
I have set up a simple bean with the available constraints. And even defined my own. There are a lot more things you can do with the validator. Too much to cover here. There are plenty of examples out there.
The next things you may consider looking at might be
Programatic configuration. Instead of using annotations.
A maven plugin that will works at build time. It will raise an error if there are problems with the way annotations are getting used.
Constraint grouping. If you want to validate parts of a bean and not the whole thing.