Studio Developer Manual / Version 2301
Table Of ContentsIf there are no suitable predefined validator classes that match particular validation requirements, you can implement custom validators.
There are three levels of validators, each of which is represented by an interface:
Interface | Purpose |
---|---|
PropertyValidator
|
A PropertyValidator validates a single property value of a content, like the
LinkListMinLengthValidator .
If you want to validate multiple properties
of a content independently of each other, use a PropertyValidator for each
property. If the properties are related with respect to validity, use a CapTypeValidator .
PropertyValidators are usually generic and can be used for various properties of different
content types.
|
CapTypeValidator
|
A CapTypeValidator validates contents of a particular content type.
CapTypeValidators usually take
multiple properties of the content into account (for example AtLeastOneNotEmptyValidator )
or verify contextual aspects of the content (for example
ChannelIsPartOfNavigationValidator ).
|
Validator
|
A Validator validates arbitrary contents. Such validators are often singletons, like the
AvailableLocalesValidator .
You will rarely need to implement a Validator , since PropertyValidator
and CapTypeValidator
offer more development convenience and suffice for most usecases.
|
Table 9.7. Levels of Validators
Property Validators
For a property validator, you have to implement the interface
PropertyValidator.
The easiest way of doing this is by extending the class
AbstractPropertyValidator<T>
and implementing the method isValid(T value)
.
public class MyValidator extends AbstractPropertyValidator<String> { public MyValidator(@NonNull String property) { super(String.class, property); } @Override protected boolean isValid(String value) { return ...; } }
Example 9.84. Implementing a property validator
The example shows a PropertyValidator
for String properties. See
CapStruct
for the possible types of property values. You can also implement property validators
for more general types, esp. for Object
, and apply them to arbitrary properties.
But the usecases for validators that are suitable for, let's say, Integer properties and Blob properties
are probably rare, so you will implement property validators for particular property types
most of the time.
Now, declare a MyValidator
for the property
teaserTitle
of the content type CMTeasable
as a Spring bean.
@Bean @ConditionalOnProperty( name = "validator.enabled.content-type-validator.my-validator-teaser-title", matchIfMissing = true) ContentTypeValidator myValidator(CapConnection con) { ContentType type = con.getContentRepository().getContentType("CMTeasable"); return new ContentTypeValidator(type, true, // include subtypes of CMTeasable List.of(new MyValidator("teaserTitle"))); }
Example 9.85. Declaring a property validator as Spring bean
As an alternative to the Spring bean declaration, you can declare validators
by Json configuration files. If you want to support this option also for your
custom validators, you must provide a factory to instantiate validators. In most
cases, this is easy: first, you enhance the constructor of your validator class with some
Jackson annotations. Your Json enabled MyValidator
class would look like this:
public class MyValidator extends AbstractPropertyValidator<String> { @JsonCreator public MyValidator(@JsonProperty(value = "property", required = true) @NonNull String property) { super(String.class, property); } @Override protected boolean isValid(String value) { return ...; } }
Example 9.86. A Json-enabled property validator
The jackson annotations originate from the com.fasterxml.jackson.core:jackson-annotations
library, which you must add to your maven dependencies.
All constructor arguments must be annotated with @JsonProperty
.
The supported types are String
, Boolean
, numbers, enums and
nested maps and lists of these types. Be aware, that non-required constructor arguments must be
nullable, that is, do not use primitive types but only the according wrapper classes like
Boolean
for such arguments.
You can use the @JsonProperty
annotation also at setter methods
(at the method, not at the argument!) or directly at the field declaration.
To generate a correct Json schema, all relevant properties have to be annotated with @JsonProperty
directly at the field (can be private) or the getter method.
Be aware that properties of simple types (for example, boolean
) will automatically be marked as required.
So the summarized recommendation is: Add @JsonProperty
to the field declarations of all
relevant properties and to all constructor arguments and don't use simple types for non-required properties.
The @JsonProperty
annotation has a value
attribute, which denotes the field
name in the Json representation of the object. By convention, the field name of the "property"
constructor argument (corresponding to the second argument of the AbstractPropertyValidator
constructor) is always property
. When applied to setter methods, the field name
should be the lowercase/hyphen variant. For example, if the method name is setFooBar
,
the field name should be foo-bar
. Adhering to these conventions, you spare a lot of
documentation, and you make life much easier for those who want to use your validator.
Next, you provide the actual validator factory as a Spring bean. A property validator factory implements the interface PropertyValidatorFactory. For Jackson-annotated validator classes, there is a generic factory class ClassBasedPropertyValidatorFactory that you can use:
@Bean public PropertyValidatorFactory myValidatorFactory() { return new ClassBasedPropertyValidatorFactory(MyValidator.class); }
Example 9.87. Providing a property validator factory
While the validator factory is a Spring bean, the validator instances are only simple POJOs.
That means, that any Spring features of the validator class, like @AutoWired
or
InitializingBean
, are not effective if a validator is instantiated by the
ClassBasedPropertyValidatorFactory
. Therefore, any mandatory configuration
of a validator should be required as constructor arguments, and any state checks should be
done in the constructor, in order to ensure a legal state of the validator.
If instantiating your validator is too complex to be expressed by Jackson annotations
(for example, because it needs injections of unsupported types, or initialization methods
must be invoked), you cannot simply use the ClassBasedPropertyValidatorFactory
,
but you must implement a custom factory of type
PropertyValidatorFactory.
The newInstance
method must return a property validator that is ready to use.
The configuration
map provides the parameters for the particular instance.
Since the factory is a Spring bean, you can have injected any additional service beans
you need to set up a property validator.
Finally, you provide a Json configuration file that declares concrete validators.
The following Json declaration is equivalent to the above Spring bean declaration
of a MyValidator
validator for the teaserTitle
property:
{ "content-type-validator": { "teasable-validation": { "content-type": "CMTeasable", "subtypes": true, "property-validators": [ { "my-validator": { "property": "teaserTitle" } } ] } } }
Example 9.88. Declaring a property validator with Json
As you know already from the Spring bean configuration, property validators must be wrapped
into content type validators. The outer map key content-type-validator
denotes
a predefined and provided factory to do this. The nested map key teasable-validation
is the validator id. You might encounter it in log messages or exceptions, so you should choose
a value that you can easily recognize. The three entries content-type
,
subtypes
and property-validators
constitute the configuration
for the content type validator. The value of property-validators
is a list
of property validator configurations. A property validator configuration is a map with
exactly one entry, whose key is the factory id for the property validator.
The ClassBasedPropertyValidatorFactory
, that you use to create MyValidator
instances, uses the lowercase/hyphen variant of the validator class as factory id,
that is my-validator
. The value of the map entry is another map which contains
the configuration for the actual property validator. If you use the
ClassBasedPropertyValidatorFactory
, this map must contain
at least values for all required constructor arguments, and optionally values for the
other @JsonProperty
annotated constructor arguments, setters or fields.
MyValidator
needs only the name of the property that is to be validated,
teaserTitle
.
Content Validators
If you want to validate a content as a whole, rather than a a single property value, you can provide a
CapTypeValidator.
You can implement it from scratch, or simply extend the
AbstractContentTypeValidator,
which leaves only the validate(Content, Issues)
method to be implemented.
public class MyContentValidator extends AbstractContentTypeValidator { private final SitesService sitesService; public MyContentValidator(@NonNull ContentType type, boolean isValidatingSubtypes, @NonNull SitesService sitesService) { super(type, isValidatingSubtypes); this.sitesService = sitesService; } @Override public void validate(Content content, Issues issues) { if (...) { issues.addIssue(Severity.ERROR, "myProperty", "myCode"); } } }
Example 9.89. Implementing a content validator
In this example, it is assumed that the validator needs the sites service for the validation. You can declare such a validator as a Spring bean:
@Bean @ConditionalOnProperty( name = "validator.enabled.my-content-validator.cm-teasable", matchIfMissing = true) CapTypeValidator myContentValidator(CapConnection con, SitesService sitesService) { ContentType type = con.getContentRepository().getContentType("CMTeasable"); return new MyContentValidator(type, true, sitesService); }
Example 9.90. Declaring a content validator as Spring bean
Just as for property validators, you should declare an application property
to disable the validator. The name pattern is the same: the prefix
validator.enabled.
, followed by the lowercase/hyphen variant of
the validator class and a short description.
Just like property validators, you can alternatively declare content validators by
Json configuration files. This requires a factory for the validators.
The validation framework provides the
ClassBasedCapTypeValidatorFactory
as a generic factory for Jackson-annotated content validators. For the
MyContentValidator
the annotations would look like this:
public class MyContentValidator extends AbstractContentTypeValidator { private final SitesService sitesService; @JsonCreator public MyContentValidator( @JsonProperty(value = "content-type", required = true) @NonNull ContentType type, @JsonProperty(value = "subtypes") @Nullable Boolean isValidatingSubtypes, @JacksonInject @NonNull SitesService sitesService)) { super(type, isValidatingSubtypes); this.sitesService = sitesService; } @Override public void validate(Content content, Issues issues) { if (...) { issues.addIssue(Severity.ERROR, "myProperty", "myCode"); } } }
Example 9.91. A Json-enabled content validator
ClassBasedCapTypeValidatorFactory
has some more features
compared to ClassBasedPropertyValidatorFactory
. In addition
to the simple types, you can also annotate ContentType
arguments
as @JsonProperty
. By convention, the Json field name of a
ContentType
argument is content-type
. If you need the
SitesService
or the CapConnection
to implement your
validation logic, you can have them injected as @JacksonInject
annotated constructor arguments. The declaration of the factory looks like this:
@Bean public CapTypeValidatorFactory myContentValidatorFactory( CapConnection connection, SitesService sitesService) { return new ClassBasedCapTypeValidatorFactory( MyContentValidator.class, connection, sitesService); }
Example 9.92. Providing a content validator factory
Be aware, that validators instantiated by ClassBasedCapTypeValidatorFactory
are no Spring beans, but simple POJOs. Do not make use of Spring features, such as
@Autowired
or InitializingBean
in your validator classes, but
require any mandatory configuration as constructor arguments.
If the instantiation of your content validator is too complex to be expressed by Jackson annotations, you can provide a custom factory. It must implement the interface CapTypeValidatorFactory
The Json equivalent to the above Spring bean declaration of the validator looks like this:
{ "my-content-validator": { "cm-teasable": { "content-type": "CMTeasable", "subtypes": true } } }
Example 9.93. Declaring a content validator with Json
The factoryId my-content-validator
is implied as the lowercase/hyphen variant
of the validator class MyContentValidator
. This validator can be disabled by
setting the application property validator.enabled.my-content-validator.cm-teasable
to false, just like the equivalent Spring bean validator.
Validators
If CapTypeValidator
is still too specific, or you do not benefit from
the features of AbstractCapTypeValidator
, you can implement a
Validator
.
This interface is so generic that there is hardly more to say about it. Since it is rarely needed,
CoreMedia does not provide any supporting convenience classes as for property validators
or content validators.
You can provide validators as Spring beans or by Json
configuration files. The possibility of Json configuration requires an according
ValidatorFactory
,
which must be provided as a Spring bean. The factory pattern is the same as for
property validators or content validators: The factoryId is used to reference the
factory from the Json configuration, and the newInstance
method is
invoked with the innermost maps of the configuration.
The configuration would look like this:
{ "my-general-validator": { "an-instance": { "foo": "bar" } "another-instance": { "foo": "42" } } }
Example 9.94. Declaring a general validator with Json
These general validators are technically decoupled from content validators and property validators.
Therefore, configuration files for such validators have a different naming pattern:
validation/general/**/*-validator-configuration.json
.