Create a Custom Validator
Introduction
Goal
Develop a custom validator to use in your document types.
Background
When defining a document type, validators can be attached to its primitive fields, compound fields, and the document type itself to make sure their values satisfy certain requirements. Bloomreach Experience Manager provides a number of validators out-of-the-box. If the out-of-the-box validators are insufficient, developers can create their own validators as explained on this page.
Validation only works for document types with workflow defined, i.e. of type hippostdpubwf:document.
Choose a Scope
Validators can have field scope, compound scope, or document scope. The table below summarized for each scope what is validated, where violations are reported, and at what level the validator can be configured:
Validator Scope |
Validates |
Reports |
Configured |
---|---|---|---|
field |
value of one field |
below the field |
per field |
compound |
values of multiple fields in a compound |
above the compound |
per field or compound type |
document |
values of multiple fields in a document |
above the document (field highlighting not supported) |
per document type |
Create a Validator Class
Create a validator class and make it extend org.onehippo.cms.services.validation.api.Validator.
As an example, see the out-of-the-box field-scoped RegExpValidator class:
public class RegExpValidator implements Validator<String> { private static final String PATTERN_KEY = "regexp.pattern"; private final Pattern pattern; public RegExpValidator(final Node config) { try { pattern = Pattern.compile(config.getProperty(PATTERN_KEY).getString()); } catch (RepositoryException e) { throw new ValidationContextException("Cannot read required property '" + PATTERN_KEY + "'", e); } } public Optional<Violation> validate(final ValidationContext context, final String value) { if (pattern.matcher(value).find()) { return Optional.empty(); } return Optional.of(context.createViolation()); } }
The API requires to implement the following methods:
-
constructor: must be public, so the system can instantiate the class via reflection. The constructor can either accept no parameters, or a single parameter of type javax.jcr.Node that holds validator-specific configuration.
- validate: performs the validation and returns a violation when errors are found. The type of the validated value should match with the JCR-level type (String, Date, Long, etc.). Compound fields and documents get a value of type javax.jcr.Node. You can retrieve all kinds of information from the context parameter, for example:
- JCR name (e.g. "myproject:title")
- JCR Type (e.g. "String" or "myproject:mycompound")
- Type (e.g. "Text", which is a special sub-type of "String")
- User locale (e.g. "en")
- User time zone (e.g. "Europe/Amsterdam")
- Parent node of the value (e.g. the node for the compound it's part of)
- Document node
Configure a Validator
To register a validator, add a new hipposys:moduleconfig node to the validation service configuration, located at /hippo:configuration/hippo:modules/validation/hippo:moduleconfig.
Best practice: use the project namespace in the name of the validator. This will make it easier for you to distinguish between default product validators and project validators.
Example of the email validator:
/hippo:configuration/hippo:modules/validation/hippo:moduleconfig/myproject:email: jcr:primaryType: hipposys:moduleconfig hipposys:className: org.onehippo.cms.services.validation.validator.RegExpValidator regexp.pattern: ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6}$
Add a Validator to (a Field in) a Document Type
To add a validator at field level (either primitive or compound), find the document type and the field you'd like to validate in the relevant namespace definition at /hippo:namespaces/[namespace]/[documenttype]/hipposysedit:nodetype/hipposysedit:nodetype/[fieldname].
Add the node name of the validator configuration as a value to the multi-valued hipposysedit:validators property.
The example below is for an 'Email' field of an 'author' document type:
/hippo:namespaces/myproject/author/hipposysedit:nodetype/hipposysedit:nodetype/email: jcr:primaryType: hipposysedit:field hipposysedit:mandatory: false hipposysedit:multiple: false hipposysedit:ordered: false hipposysedit:path: myproject:email hipposysedit:primary: false hipposysedit:type: String hipposysedit:validators: [myproject:email]
To add a validator at document type level, add the node name of the validator configuration as a value to the multi-valued hipposysedit:validators property to /hippo:namespaces/[namespace]/[documenttype]/hipposysedit:nodetype/hipposysedit:nodetype.
Configure Validation Messages
The last step is to create a validation message. Validator messages are stored as repository resource bundles at /hippo:configuration/hippo:translations/hippo:cms/validators/.
For each of the languages in your project, add a translation property with the same name as the validator configuration with the appropriate violation message to the resource bundle of the target language. For example:
/en: jcr:primaryType: hipposys:resourcebundle myproject:email: Enter a valid email address escaped: 'Do not use these characters: < > & '' "' image-references: Select an image non-empty: Enter a value
As can be seen in the example above, validation messages generally have the form of an imperative sentence such as "Enter a valid email address". For consistency, it is recommended you do the same in your custom validators.
Note that it is possible to define multiple violation messages for a validator, in which case the property name is composed as <validator-name>#<subKey> such as in the example below:
/en: jcr:primaryType: hipposys:resourcebundle myproject:customvalidator: Custom validation message myproject:customvalidator#alternate: Alternate validation message
Note that within a validator class, it's possible to access translated violations through the validation context using the context.createViolation() method and access alternate violations using the context.createViolation(String subKey) method.
Verify That the Custom Validator Works Correctly
Troubleshooting
Problem: the document editor shows a message "This document has 1 error" but no actual validation error message is displayed below the field causing the validation error.
Probable cause: the frontend:references and frontend:services properties of the document type editor template's _default_ node are missing the "validator.id" value. They should have been added by default by the document type editor when creating the document type but in some implementation projects they may be missing due to, for example, custom YAML editing, certain upgrade scenarios, etc.
Solution: add the value "validator.id" to the multi-valued frontend:references and frontend:services properties of the document type editor template's _default_ node.
Example, for an "author" document type in the project "myproject":
/hippo:namespaces/myproject/author/editor:templates/_default_: jcr:primaryType: frontend:plugincluster frontend:properties: [mode] frontend:references: [wicket.model, model.compareTo, engine, validator.id] frontend:services: [wicket.id, validator.id]