May 15, 2020
Estimated Post Reading Time ~

AEM Meets Sling Validation

A short introduction to Sling Validation - content-based validation framework. A practical use case on how to incorporate Sling Validation into your AEM project.


Content validation for AEM
What if we had a validation mechanism in AEM that validates our resources on demand? We could define our own validation rules and notify content editors when validation fails. Validation rules could be simple (e.g. checking mandatory fields) or complex (e.g. validating a number and type of components inside a container component). In addition, we could validate an AEM page just before it gets published. Once again just to be 110% sure! Sounds great! Is this even possible in AEM?

How to implement sling validation in AEM
We can achieve all of this using Sling Validation and a little magic. The validation project started with SLING-2803 in 2013 and the current implementation can be found at Apache Sling repository. Open-source software community is still working towards release 1.0 and there might be API changes in the future. Therefore, AEM 6.1 is not shipped with Sling Validation yet, but you can easily add it to your project.

Let's dive in by exploring how we can employ Sling Validation to validate a number of components inside a container component. The out of the box AEM paragraph system does not restrict the number of components editors can drag&drop inside the paragraph system. We can write our validation rules as follows: container component cannot have less than 2 and more than 6 components. Now we will put these validation rules inside the Validation Model.

Validation model
Here is a real life example. Let's validate the Layout Container component that comes with AEM 6.1. This standard component is a paragraph system that contains other components and its resource can be found at "/libs/wcm/foundation/components/responsivegrid". The resource is bound to a Validation Model by constructing a data structure:
Jump to CRXDE Lite and create a new folder somewhere close to your components, e.g. /apps/geometrixx-media/components/validation.
Now create a new node "validation.model.responsivegrid” and add two mandatory properties: "sling:resourceType" and "validatedResourceType".



A value of "sling:resourceType" must be "sling/validation/model" otherwise Sling Validation will not find it! A property "validatedResourceType" binds your validation model with a resource for which validation will be applied.
Now specify the resource property to validate and the validator to use by creating the following structure:



Here, we validate property "sling:resourceType" of resource "wcm/foundation/components/responsivegrid" using java validator class "com.example.validators.MinMaxValidator".

We can make property "sling:resourceType" mandatory by adding property "optional" with value "false".
Finally, add property “validatorArguments” in order to provide arguments to our validator, e.g. container component can have only 2 to 6 child components.

That's it, we have created the following data structure for our Validation Model:

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0"
xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="nt:unstructured"
sling:resourceType="sling/validation/model"
validatedResourceType="wcm/foundation/components/responsivegrid">
<properties jcr:primaryType="nt:unstructured">
<sling:resourceType jcr:primaryType="nt:unstructured" optional="{Boolean}false">
<validators jcr:primaryType="nt:unstructured">
<com.example.validators.MinMaxValidator jcr:primaryType="nt:unstructured"
validatorArguments="[minNumberAllowed=2,maxNumberRequired=6]"/>
</validators>
</sling:resourceType>
</properties>
</jcr:root>


It’s time to have some coding fun and write our validation logic.

Validator
The validation is going to live inside your validator class that implements Validator interface:

package com.example.validators;
import ...;

@Component
@Service(value = Validator.class)
public class MinMaxValidator implements Validator<String> {
public static final String MAX_NUMBER_ARGUMENT = "maxNumberRequired";
public static final String MIN_NUMBER_ARGUMENT = "minNumberAllowed";

@Override
public String validate(final String data, final ValueMap properties, final Resource containerResource, final ValueMap validationArguments)
throws SlingValidationException {
final Integer maxAllowedComponents = validationArguments.get(MAX_NUMBER_ARGUMENT, Integer.class);
final Integer minRequiredComponents = validationArguments.get(MIN_NUMBER_ARGUMENT, Integer.class);

if (maxAllowedComponents == null || minRequiredComponents == null) {
throw new SlingValidationException("Mandatory arguments are missing!");
}

final int numberOfChildComponents = Iterables.size(containerResource.getChildren());

if (numberOfChildComponents < minRequiredComponents) {
return String.format("Minimum %s component(s) required!", minRequiredComponents);
}
if (numberOfChildComponents > maxAllowedComponents) {
return String.format("Maximum %s component(s) allowed!", maxAllowedComponents);
}

// validation passed successfully
return null;
}
}


Notice how we get the validation arguments and the resource being validated: We return a formatted error message if the number of child components exceeds the limits.

Now it's time to decide when we are going to trigger validation.

Validation service
First option. The ValidationService OSGi service provides convenient methods to retrieve correct ValidationModel and validate chosen resource with that model:

@Reference
private ValidationService validationService;
...
ValidationModel validationModel = validationService.getValidationModel(containerResource, true);
ValidationResult validationResult = validationService.validate(containerResource, validationModel);
...


Second option. Sling Models will find the right Validation Model and will trigger validation on the resource for you. All you have to do is to use attribute "validation" on the Model annotation. Currently Sling Models is able to throw validation exceptions only if the model is instantiated with the ModelFactory. It's opposite to Sling Adapter framework that swallows validation exceptions because the adaptTo() method is supposed to return null and never throw an exception.

Validate page in workflow
Once all validation models are in place, it's relatively easy to validate pages during publishing/activation process. To implement such automatic validation, an activation workflow could include a custom workflow step where the Validation Service can be injected. The service has a convenient method validateResourceRecursively that validates all child resources recursively having their own Validation Model configured. All we have to do is to pass a page content resource:

@Reference
private ValidationService validationService;
..
// Validate page resource and all child resources recursively using Sling Validation
ValidationResult result = validationService.validateResourceRecursively(page.getContentResource(), false, null, true);

Validation errors
Now we know how to trigger validation, but how do we handle validation failures? Depending on the validation outcome, the whole component can be either rendered or not. Sling Validation does not provide functionality to set a severity level of the validation message. There is no way to distinguish between validation errors, which make rendering of the component impossible (e.g. missing required field), and validation warnings, which try to render the component (e.g. too many characters). Despite this limitation, we can still nicely notify our editors that there was a validation error by implementing a custom Sling Component Filter. This filter handles validation exceptions and appends a small HTML snippet that adds a tooltip like qtip2.com to the component:


The advantages of sling validation
A newly introduced Sling Validation can make your application more robust with less boilerplate code. In addition, it opens endless possibilities for content validation. Writing validators for a number, type, or order validation is straightforward. Unfortunately, there is no connection towards Classic UI/Touch UI form validation and therefore those validation rules have to be implemented separately. It's worth reminding that Sling Validation is still in development and your contribution is more than welcome!



By aem4beginner

No comments:

Post a Comment

If you have any doubts or questions, please let us know.