close

Filter

loading table of contents...

Studio Developer Manual / Version 2207

Table Of Contents

9.21.1.3 Custom Validators

If 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:

InterfacePurpose
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.98. 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 the property teaserTitle of the document 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.99. 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.100. 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 Booleanfor 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.101. 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.102. 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.103. 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.104. 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.105. 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.106. 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.107. 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.108. 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.

Search Results

Table Of Contents
warning

Your Internet Explorer is no longer supported.

Please use Mozilla Firefox, Google Chrome, or Microsoft Edge.